"""Deal Desk plan builder wrapper."""

from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Any, Iterable

from g_gremlin.autopilot.contracts import PlanRisk, PlanSource, PlanStep, PlanV1
from g_gremlin.autopilot import planner as base_planner
from g_gremlin.autopilot.tools import AutopilotToolDefinition, ToolRegistry
from g_gremlin.scout.models import EvidenceRow, RecommendedAction, ScoutFinding, ScoutScanResult

from .close_packet import write_close_packets
from .config import DealDeskConfig
from .eventing import (
    build_pattern_update_event,
    resolve_crm_org_id,
    save_pattern_update_events,
    update_context_for_close_packet,
    utc_now_iso,
)
from .store import DealDeskStore


logger = logging.getLogger(__name__)


def _row_status(row: EvidenceRow) -> str:
    fields = row.fields or {}
    check_results = fields.get("check_results", []) or []
    for item in check_results:
        if str(item.get("status")).lower() == "fail":
            return "fail"
    return "pass"


def _has_proposed_diffs(row: EvidenceRow) -> bool:
    fields = row.fields or {}
    proposed = fields.get("proposed_diffs", {}) or {}
    return isinstance(proposed, dict) and bool(proposed)


def _clone_finding(base: ScoutFinding, rows: Iterable[EvidenceRow]) -> ScoutFinding:
    row_list = list(rows)
    return ScoutFinding(
        scout_name=base.scout_name,
        display_name=base.display_name,
        description=base.description,
        category=base.category,
        severity=base.severity,
        total_records_scanned=base.total_records_scanned,
        findings_count=len(row_list),
        evidence=row_list,
        recommended_actions=[],
        soql_queries=list(base.soql_queries),
        duration_ms=base.duration_ms,
        parameters_used=base.parameters_used,
        status=base.status,
        warnings=list(base.warnings or []),
    )


def _record_close_packet_events(
    *,
    scan_result: ScoutScanResult,
    config: DealDeskConfig,
    state_dir: Path,
    index_path: Path,
) -> None:
    try:
        payload = json.loads(index_path.read_text(encoding="utf-8"))
    except Exception as exc:
        logger.warning("Failed to load close packet index for event emission: %s", exc)
        return

    close_packets = payload.get("close_packets") if isinstance(payload, dict) else []
    packet_entries: dict[str, list[dict[str, Any]]] = {}
    for entry in close_packets or []:
        if not isinstance(entry, dict):
            continue
        opportunity_id = str(entry.get("opportunity_id") or "").strip()
        if not opportunity_id:
            continue
        packet_entries.setdefault(opportunity_id, []).append(entry)

    org_alias = scan_result.org_alias
    crm_org_id = resolve_crm_org_id(org_alias=org_alias)
    observed_at = str(payload.get("generated_at") or scan_result.ended_at or scan_result.started_at or "")
    observed_at = observed_at.strip() or utc_now_iso()
    store = DealDeskStore(state_dir)

    events = []
    for finding in scan_result.findings:
        if not finding.scout_name.startswith("dealdesk"):
            continue
        for row in finding.evidence:
            opportunity_id = str(row.record_id or "").strip()
            if not opportunity_id:
                continue
            entry_bucket = packet_entries.get(opportunity_id) or []
            packet_meta = entry_bucket.pop(0) if entry_bucket else {}
            raw_fields = dict(row.fields or {})
            raw_record = {"Id": opportunity_id, **raw_fields}
            json_path = str(packet_meta.get("json_path") or "").strip()
            markdown_path = str(packet_meta.get("markdown_path") or "").strip() or None
            overall_status = str(
                packet_meta.get("overall_status") or raw_fields.get("overall_status") or "unknown"
            ).strip() or "unknown"
            source_ref = json_path or f"artifacts/{scan_result.run_id}/close_packets/{opportunity_id}.json"
            events.append(
                build_pattern_update_event(
                    source_type="close_packet",
                    crm_org_id=crm_org_id,
                    opportunity_id=opportunity_id,
                    source_ref=source_ref,
                    observed_at=observed_at,
                    raw_record=raw_record,
                    confidence=0.82 if overall_status == "pass" else 0.74,
                    outcome=overall_status,
                    custom={"scan_run_id": scan_result.run_id, "finding": finding.scout_name},
                    evidence_ids=[opportunity_id, finding.scout_name],
                )
            )
            if json_path:
                update_context_for_close_packet(
                    store=store,
                    crm_org_id=crm_org_id,
                    org_alias=org_alias,
                    opportunity_id=opportunity_id,
                    observed_at=observed_at,
                    close_packet_json_path=json_path,
                    close_packet_markdown_path=markdown_path,
                    overall_status=overall_status,
                    account_id=str(raw_fields.get("AccountId") or "").strip() or None,
                )

    save_pattern_update_events(
        store=store,
        events=events,
        org_alias=org_alias,
        config=config,
        refresh_profile=True,
    )


def _build_steps_for_rows(
    *,
    rows: list[EvidenceRow],
    base_finding: ScoutFinding,
    action: RecommendedAction,
    tool_def: AutopilotToolDefinition | None,
    run_id: str,
    step_index: int,
    config_path: Path | None,
) -> tuple[list[PlanStep], list[AutopilotToolDefinition | None], int]:
    steps: list[PlanStep] = []
    tool_defs: list[AutopilotToolDefinition | None] = []

    for row in rows:
        if action.action_type == "sfdc.record.update" and not _has_proposed_diffs(row):
            continue
        temp_finding = _clone_finding(base_finding, [row])
        step_id = f"s{step_index}"
        step_index += 1
        step = base_planner._build_step(  # type: ignore[attr-defined]
            step_id=step_id,
            run_id=run_id,
            finding=temp_finding,
            action=action,
            tool_name=action.action_type,
            tool_def=tool_def,
        )
        if config_path:
            step.args["config_path"] = str(config_path)
        step.args["run_id"] = run_id
        steps.append(step)
        tool_defs.append(tool_def)

    return steps, tool_defs, step_index


def build_dealdesk_plan(
    scan_result: ScoutScanResult,
    registry: ToolRegistry,
    config: DealDeskConfig,
    *,
    state_dir: Path | None = None,
    artifact_base_url: str | None = None,
    config_path: Path | None = None,
) -> PlanV1:
    run_id = scan_result.run_id

    if state_dir:
        index_path = write_close_packets(scan_result, config, state_dir, artifact_base_url=artifact_base_url)
        _record_close_packet_events(
            scan_result=scan_result,
            config=config,
            state_dir=Path(state_dir),
            index_path=index_path,
        )
        try:
            receipts_dir = Path(state_dir) / "receipts" / run_id
            receipts_dir.mkdir(parents=True, exist_ok=True)
            (receipts_dir / "close_packets_index.json").write_text(
                index_path.read_text(encoding="utf-8"),
                encoding="utf-8",
            )
        except Exception as exc:
            logger.warning("Failed to copy close_packets_index.json: %s", exc)

    dealdesk_findings = [f for f in scan_result.findings if f.scout_name.startswith("dealdesk")]
    steps: list[PlanStep] = []
    step_tool_defs: list[AutopilotToolDefinition | None] = []
    evidence_rows: list[EvidenceRow] = []
    step_index = 1

    for finding in dealdesk_findings:
        passing_rows: list[EvidenceRow] = []
        failing_rows: list[EvidenceRow] = []
        for row in finding.evidence:
            if _row_status(row) == "fail":
                failing_rows.append(row)
            else:
                passing_rows.append(row)
        evidence_rows.extend(list(finding.evidence))

        update_action = RecommendedAction(
            action_type="sfdc.record.update",
            label="Fix missing/incorrect fields",
            risk_level=1,
            details={"sobject": "Opportunity", "policy_tags": ["dealdesk", "reversible_write"]},
        )
        renewal_action = RecommendedAction(
            action_type="dealdesk.renewal.create_shell",
            label="Create renewal opportunity shell",
            risk_level=2,
            details={"policy_tags": ["dealdesk"]},
        )
        stamp_action = RecommendedAction(
            action_type="dealdesk.approval.stamp",
            label="Stamp Deal Desk approval",
            risk_level=1,
            details={"policy_tags": ["dealdesk", "reversible_write"]},
        )

        update_tool = registry.get_tool(update_action.action_type)
        renewal_tool = registry.get_tool(renewal_action.action_type)
        stamp_tool = registry.get_tool(stamp_action.action_type)

        update_steps, update_defs, step_index = _build_steps_for_rows(
            rows=failing_rows,
            base_finding=finding,
            action=update_action,
            tool_def=update_tool,
            run_id=run_id,
            step_index=step_index,
            config_path=config_path,
        )
        steps.extend(update_steps)
        step_tool_defs.extend(update_defs)

        renewal_rows = [row for row in passing_rows if config.renewal.enabled]
        renewal_steps, renewal_defs, step_index = _build_steps_for_rows(
            rows=renewal_rows,
            base_finding=finding,
            action=renewal_action,
            tool_def=renewal_tool,
            run_id=run_id,
            step_index=step_index,
            config_path=config_path,
        )
        steps.extend(renewal_steps)
        step_tool_defs.extend(renewal_defs)

        stamp_steps, stamp_defs, step_index = _build_steps_for_rows(
            rows=passing_rows,
            base_finding=finding,
            action=stamp_action,
            tool_def=stamp_tool,
            run_id=run_id,
            step_index=step_index,
            config_path=config_path,
        )
        steps.extend(stamp_steps)
        step_tool_defs.extend(stamp_defs)

    risk: PlanRisk = base_planner._compute_plan_risk(  # type: ignore[attr-defined]
        steps, step_tool_defs, evidence_rows
    )
    approvals_required = risk.max_risk_level >= 1

    total_fail = sum(1 for row in evidence_rows if _row_status(row) == "fail")
    total_pass = sum(1 for row in evidence_rows if _row_status(row) == "pass")
    summary = f"Deal Desk: {total_fail} failing, {total_pass} passing -> {len(steps)} step(s)"

    return PlanV1(
        version=1,
        run_id=run_id,
        source=PlanSource(type="scout_scan", scan_id=run_id),
        summary=summary,
        risk=risk,
        plan_hash=None,
        policy_hash=None,
        steps=steps,
        verification=["dealdesk_close_verification"],
        approvals_required=approvals_required,
    )


__all__ = ["build_dealdesk_plan"]
