# #region OrchestratorHelpers [C:4] [TYPE Module] [SEMANTICS dataset, review, snapshot, fingerprint, recovery]
# @BRIEF Pure helper methods extracted from DatasetReviewOrchestrator for INV_7 compliance — snapshot, blockers, fingerprint, recovery bootstrap.
# @LAYER Domain
# @RELATION DEPENDS_ON -> [DatasetReviewModels]
# @RELATION DEPENDS_ON -> [SupersetContextExtractor]
# @PRE  Caller provides a loaded session aggregate with hydrated child collections.
# @POST  Helper results are deterministic and do not mutate persistence directly.

from __future__ import annotations

from datetime import datetime
import hashlib
import json
from typing import Any, cast

from src.core.logger import logger
from src.models.dataset_review import (
    ApprovalState,
    BusinessSummarySource,
    CompiledPreview,
    ConfidenceState,
    DatasetProfile,
    DatasetReviewSession,
    FindingArea,
    FindingSeverity,
    PreviewStatus,
    ResolutionState,
    ValidationFinding,
)

logger = cast(Any, logger)


# #region parse_dataset_selection [C:2] [TYPE Function]
# @BRIEF Normalize dataset-selection payload into canonical session references.
def parse_dataset_selection(source_input: str) -> tuple[str, int | None]:
    normalized = str(source_input or "").strip()
    if not normalized:
        raise ValueError("dataset selection input must be non-empty")
    if normalized.isdigit():
        dataset_id = int(normalized)
        return f"dataset:{dataset_id}", dataset_id
    if normalized.startswith("dataset:"):
        suffix = normalized.split(":", 1)[1].strip()
        if suffix.isdigit():
            return normalized, int(suffix)
        return normalized, None
    return normalized, None


# #endregion parse_dataset_selection


# #region build_initial_profile [C:2] [TYPE Function]
# @BRIEF Create the first profile snapshot so exports and detail views remain usable immediately after intake.
def build_initial_profile(
    session_id: str,
    parsed_context: Any | None,
    dataset_ref: str,
) -> DatasetProfile:
    dataset_name = (
        dataset_ref.split(".")[-1] if dataset_ref else "Unresolved dataset"
    )
    business_summary = (
        f"Review session initialized for {dataset_ref}."
        if dataset_ref
        else "Review session initialized with unresolved dataset context."
    )
    confidence_state = (
        ConfidenceState.MIXED
        if parsed_context and getattr(parsed_context, "partial_recovery", False)
        else ConfidenceState.MOSTLY_CONFIRMED
    )
    return DatasetProfile(
        session_id=session_id,
        dataset_name=dataset_name or "Unresolved dataset",
        schema_name=dataset_ref.split(".")[0] if "." in dataset_ref else None,
        business_summary=business_summary,
        business_summary_source=BusinessSummarySource.IMPORTED,
        description="Initial review profile created from source intake.",
        dataset_type="unknown",
        is_sqllab_view=False,
        completeness_score=0.25,
        confidence_state=confidence_state,
        has_blocking_findings=False,
        has_warning_findings=bool(
            parsed_context and getattr(parsed_context, "partial_recovery", False)
        ),
        manual_summary_locked=False,
    )


# #endregion build_initial_profile


# #region build_partial_recovery_findings [C:3] [TYPE Function]
# @BRIEF Project partial Superset intake recovery into explicit findings without blocking session usability.
# @PRE  parsed_context.partial_recovery is true.
# @POST  Returns warning-level findings that preserve usable but incomplete state.
def build_partial_recovery_findings(parsed_context: Any) -> list[ValidationFinding]:
    findings: list[ValidationFinding] = []
    for unresolved_ref in getattr(parsed_context, "unresolved_references", []):
        findings.append(
            ValidationFinding(
                area=FindingArea.SOURCE_INTAKE,
                severity=FindingSeverity.WARNING,
                code="PARTIAL_SUPERSET_RECOVERY",
                title="Superset context recovered partially",
                message=(
                    "Session remains usable, but some Superset context requires review: "
                    f"{unresolved_ref.replace('_', ' ')}."
                ),
                resolution_state=ResolutionState.OPEN,
                caused_by_ref=unresolved_ref,
            )
        )
    return findings


# #endregion build_partial_recovery_findings


# #region extract_effective_filter_value [C:2] [TYPE Function]
# @BRIEF Separate normalized filter payload metadata from the user-facing effective filter value.
def extract_effective_filter_value(
    normalized_value: Any, raw_value: Any
) -> Any:
    if isinstance(normalized_value, dict) and (
        "filter_clauses" in normalized_value
        or "extra_form_data" in normalized_value
    ):
        return raw_value
    return normalized_value if normalized_value is not None else raw_value


# #endregion extract_effective_filter_value


# #region build_execution_snapshot [C:4] [TYPE Function]
# @BRIEF Build effective filters, template params, approvals, and fingerprint for preview and launch gating.
# @PRE  Session aggregate includes imported filters, template variables, and current execution mappings.
# @POST  Returns deterministic execution snapshot for current session state without mutating persistence.
def build_execution_snapshot(session: DatasetReviewSession) -> dict[str, Any]:
    session_record = cast(Any, session)
    filter_lookup = {
        item.filter_id: item for item in session_record.imported_filters
    }
    variable_lookup = {
        item.variable_id: item for item in session_record.template_variables
    }

    effective_filters: list[dict[str, Any]] = []
    template_params: dict[str, Any] = {}
    approved_mapping_ids: list[str] = []
    open_warning_refs: list[str] = []
    preview_blockers: list[str] = []
    mapped_filter_ids: set[str] = set()

    for mapping in session_record.execution_mappings:
        imported_filter = filter_lookup.get(mapping.filter_id)
        template_variable = variable_lookup.get(mapping.variable_id)
        if imported_filter is None:
            preview_blockers.append(f"mapping:{mapping.mapping_id}:missing_filter")
            continue
        if template_variable is None:
            preview_blockers.append(f"mapping:{mapping.mapping_id}:missing_variable")
            continue

        effective_value = mapping.effective_value
        if effective_value is None:
            effective_value = extract_effective_filter_value(
                imported_filter.normalized_value, imported_filter.raw_value,
            )
        if effective_value is None:
            effective_value = template_variable.default_value

        if effective_value is None and template_variable.is_required:
            preview_blockers.append(
                f"variable:{template_variable.variable_name}:missing_required_value"
            )
            continue

        mapped_filter_ids.add(imported_filter.filter_id)
        if effective_value is not None:
            mapped_filter_payload = {
                "mapping_id": mapping.mapping_id,
                "filter_id": imported_filter.filter_id,
                "filter_name": imported_filter.filter_name,
                "variable_id": template_variable.variable_id,
                "variable_name": template_variable.variable_name,
                "effective_value": effective_value,
                "raw_input_value": mapping.raw_input_value,
            }
            if isinstance(imported_filter.normalized_value, dict):
                mapped_filter_payload["display_name"] = imported_filter.display_name
                mapped_filter_payload["normalized_filter_payload"] = (
                    imported_filter.normalized_value
                )
            effective_filters.append(mapped_filter_payload)
        template_params[template_variable.variable_name] = effective_value
        if mapping.approval_state == ApprovalState.APPROVED:
            approved_mapping_ids.append(mapping.mapping_id)
        if (
            mapping.requires_explicit_approval
            and mapping.approval_state != ApprovalState.APPROVED
        ):
            open_warning_refs.append(mapping.mapping_id)

    for imported_filter in session_record.imported_filters:
        if imported_filter.filter_id in mapped_filter_ids:
            continue
        effective_value = extract_effective_filter_value(
            imported_filter.normalized_value, imported_filter.raw_value,
        )
        if effective_value is None:
            continue
        effective_filters.append(
            {
                "filter_id": imported_filter.filter_id,
                "filter_name": imported_filter.filter_name,
                "display_name": imported_filter.display_name,
                "effective_value": effective_value,
                "raw_input_value": imported_filter.raw_value,
                "normalized_filter_payload": imported_filter.normalized_value,
            }
        )

    mapped_variable_ids = {
        mapping.variable_id for mapping in session_record.execution_mappings
    }
    for variable in session_record.template_variables:
        if variable.variable_id in mapped_variable_ids:
            continue
        if variable.default_value is not None:
            template_params[variable.variable_name] = variable.default_value
            continue
        if variable.is_required:
            preview_blockers.append(f"variable:{variable.variable_name}:unmapped")

    semantic_decision_refs = [
        field.field_id
        for field in session.semantic_fields
        if field.is_locked
        or not field.needs_review
        or field.provenance.value != "unresolved"
    ]
    preview_fingerprint = compute_preview_fingerprint(
        {
            "dataset_id": session_record.dataset_id,
            "template_params": template_params,
            "effective_filters": effective_filters,
        }
    )
    return {
        "effective_filters": effective_filters,
        "template_params": template_params,
        "approved_mapping_ids": sorted(approved_mapping_ids),
        "semantic_decision_refs": sorted(semantic_decision_refs),
        "open_warning_refs": sorted(open_warning_refs),
        "preview_blockers": sorted(set(preview_blockers)),
        "preview_fingerprint": preview_fingerprint,
    }


# #endregion build_execution_snapshot


# #region build_launch_blockers [C:3] [TYPE Function]
# @BRIEF Enforce launch gates from findings, approvals, and current preview truth.
# @PRE  execution_snapshot was computed from current session state.
# @POST  Returns explicit blocker codes for every unmet launch invariant.
def build_launch_blockers(
    session: DatasetReviewSession,
    execution_snapshot: dict[str, Any],
    preview: CompiledPreview | None,
) -> list[str]:
    session_record = cast(Any, session)
    blockers = list(execution_snapshot["preview_blockers"])

    for finding in session_record.findings:
        if (
            finding.severity == FindingSeverity.BLOCKING
            and finding.resolution_state
            not in {ResolutionState.RESOLVED, ResolutionState.APPROVED}
        ):
            blockers.append(f"finding:{finding.code}:blocking")
    for mapping in session_record.execution_mappings:
        if (
            mapping.requires_explicit_approval
            and mapping.approval_state != ApprovalState.APPROVED
        ):
            blockers.append(f"mapping:{mapping.mapping_id}:approval_required")

    if preview is None:
        blockers.append("preview:missing")
    else:
        if preview.preview_status != PreviewStatus.READY:
            blockers.append(f"preview:{preview.preview_status.value}")
        if preview.preview_fingerprint != execution_snapshot["preview_fingerprint"]:
            blockers.append("preview:fingerprint_mismatch")

    return sorted(set(blockers))


# #endregion build_launch_blockers


# #region get_latest_preview [C:2] [TYPE Function]
# @BRIEF Resolve the current latest preview snapshot for one session aggregate.
def get_latest_preview(session: DatasetReviewSession) -> CompiledPreview | None:
    session_record = cast(Any, session)
    if not session_record.previews:
        return None
    if session_record.last_preview_id:
        for preview in session_record.previews:
            if preview.preview_id == session_record.last_preview_id:
                return preview
    return sorted(
        session_record.previews,
        key=lambda item: (item.created_at or datetime.min, item.preview_id),
        reverse=True,
    )[0]


# #endregion get_latest_preview


# #region compute_preview_fingerprint [C:1] [TYPE Function]
# @BRIEF Produce deterministic execution fingerprint for preview truth and staleness checks.
def compute_preview_fingerprint(payload: dict[str, Any]) -> str:
    serialized = json.dumps(payload, sort_keys=True, default=str)
    return hashlib.sha256(serialized.encode("utf-8")).hexdigest()


# #endregion compute_preview_fingerprint


# #endregion OrchestratorHelpers
