"""Deal Desk capability refresh helpers and receipts."""

from __future__ import annotations

import secrets
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, computed_field

from g_gremlin.autopilot.ai_provider import AIProviderStore

from .config import DEFAULT_CONFIG_PATH, DealDeskConfig, load_dealdesk_config
from .eventing import resolve_crm_org_id
from .field_resolution import build_dealdesk_config_draft, build_quote_environment_profile, discover_quote_object_profiles
from .profiles import QuoteEnvironmentProfileV1
from .store import DealDeskStore


class _CapabilityRefreshModel(BaseModel):
    model_config = ConfigDict(extra="ignore")

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)


class DealDeskCapabilityRefreshChangeV1(_CapabilityRefreshModel):
    path: str
    before: Any = None
    after: Any = None


class DealDeskCapabilityRefreshReceiptV1(_CapabilityRefreshModel):
    version: int = 1
    receipt_id: str
    receipt_path: str
    created_at: str
    actor_id: str
    actor_email: str | None = None
    action: str = "refresh_capabilities"
    target_kind: str = "capability_refresh"
    crm_org_id: str
    org_alias: str | None = None
    status: str
    error_message: str | None = None
    config_base_source: str
    config_base_path: str
    generated_config_preserved: bool = True
    existing_generated_config_path: str | None = None
    environment_profile_path_before: str | None = None
    environment_profile_path_after: str | None = None
    draft_config_path_before: str | None = None
    draft_config_path_after: str | None = None
    before_environment_profile: QuoteEnvironmentProfileV1 | None = None
    after_environment_profile: QuoteEnvironmentProfileV1 | None = None
    before_draft_config: DealDeskConfig | None = None
    after_draft_config: DealDeskConfig | None = None
    profile_changes: list[DealDeskCapabilityRefreshChangeV1] = Field(default_factory=list)
    draft_changes: list[DealDeskCapabilityRefreshChangeV1] = Field(default_factory=list)
    notes: list[str] = Field(default_factory=list)

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "DealDeskCapabilityRefreshReceipt/v1"


class DealDeskCapabilityRefreshReceiptSummaryV1(_CapabilityRefreshModel):
    version: int = 1
    receipt_id: str
    receipt_path: str
    created_at: str
    actor_id: str
    actor_email: str | None = None
    status: str
    error_message: str | None = None
    crm_org_id: str
    org_alias: str | None = None
    generated_config_preserved: bool = True
    config_base_source: str
    config_base_path: str
    existing_generated_config_path: str | None = None
    environment_profile_path_after: str | None = None
    draft_config_path_after: str | None = None
    profile_change_count: int = 0
    draft_change_count: int = 0
    notes: list[str] = Field(default_factory=list)

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "DealDeskCapabilityRefreshReceiptSummary/v1"


class RefreshDealDeskCapabilitiesRequestV1(_CapabilityRefreshModel):
    version: int = 1
    crm_org_id: str | None = None
    org_alias: str | None = None

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "RefreshDealDeskCapabilitiesRequest/v1"


class RefreshDealDeskCapabilitiesResultV1(_CapabilityRefreshModel):
    version: int = 1
    status: str
    message: str
    receipt: DealDeskCapabilityRefreshReceiptV1 | None = None

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "RefreshDealDeskCapabilitiesResult/v1"


def refresh_dealdesk_capability_snapshot(
    state_dir: str | Path | None,
    *,
    request: RefreshDealDeskCapabilitiesRequestV1,
    actor_id: str,
    actor_email: str | None = None,
) -> RefreshDealDeskCapabilitiesResultV1:
    store = DealDeskStore(state_dir)
    normalized_org_alias = str(request.org_alias or "").strip() or None
    normalized_crm_org_id = str(request.crm_org_id or "").strip() or None
    if normalized_crm_org_id is None and normalized_org_alias is None:
        raise RuntimeError("crm_org_id or org_alias is required for capability refresh.")

    resolved_crm_org_id = resolve_crm_org_id(
        org_alias=normalized_org_alias,
        crm_org_id=normalized_crm_org_id,
    )
    created_at = datetime.now(timezone.utc).isoformat()
    receipt_id = f"ddcap_{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')}_{secrets.token_hex(4)}"
    receipt_path = store.capability_refresh_receipt_path(
        receipt_id=receipt_id,
        crm_org_id=resolved_crm_org_id,
        org_alias=normalized_org_alias,
    )
    before_profile = store.load_quote_environment_profile(
        crm_org_id=resolved_crm_org_id,
        org_alias=normalized_org_alias,
    )
    before_profile_path = store.quote_environment_profile_path(
        crm_org_id=resolved_crm_org_id,
        org_alias=normalized_org_alias,
    )
    latest_receipt = store.find_latest_capability_refresh_receipt(
        crm_org_id=resolved_crm_org_id,
        org_alias=normalized_org_alias,
    )
    before_draft = None
    before_draft_path = None
    if latest_receipt is not None and str(latest_receipt.draft_config_path_after or "").strip():
        before_draft_path = str(latest_receipt.draft_config_path_after).strip()
        before_draft = store.load_capability_refresh_draft(draft_path=before_draft_path)

    generated_config = store.load_generated_config(
        crm_org_id=resolved_crm_org_id,
        org_alias=normalized_org_alias,
    )
    base_config = generated_config or load_dealdesk_config()
    config_base_source = "generated" if generated_config is not None else "default"
    config_base_path = (
        str(store.generated_config_path(crm_org_id=resolved_crm_org_id, org_alias=normalized_org_alias))
        if generated_config is not None
        else str(DEFAULT_CONFIG_PATH)
    )
    existing_generated_config_path = config_base_path if generated_config is not None else None

    ai_store = AIProviderStore(store.state_dir)
    ai_config = ai_store.load_config()
    notes: list[str] = []
    error_message = None
    status = "completed"
    after_profile = None
    after_profile_path = None
    after_draft = None
    after_draft_path = None

    object_profiles, fields_by_object = discover_quote_object_profiles(normalized_org_alias)
    if generated_config is not None:
        notes.append(
            "Existing generated config was preserved; refresh wrote a separate draft artifact instead of overwriting live Deal Desk settings."
        )
    else:
        notes.append(
            "No generated config existed for this scope; refresh still wrote a separate draft artifact for explicit promotion."
        )

    if not object_profiles:
        notes.append(
            "Discovery did not find a supported quote stack via CRM describe, so the refreshed capability profile remains conservative."
        )

    try:
        after_profile = build_quote_environment_profile(
            crm_org_id=resolved_crm_org_id,
            generated_at=created_at,
            org_alias=normalized_org_alias,
            ai_config=ai_config,
            object_profiles=object_profiles,
            fields_by_object=fields_by_object,
        )
    except Exception as exc:
        error_message = str(exc)
        if ai_config is not None:
            notes.append(
                "AI-assisted field mapping failed during discovery; refresh retried without AI adjustments and persisted the fallback result."
            )
            try:
                after_profile = build_quote_environment_profile(
                    crm_org_id=resolved_crm_org_id,
                    generated_at=created_at,
                    org_alias=normalized_org_alias,
                    ai_config=None,
                    object_profiles=object_profiles,
                    fields_by_object=fields_by_object,
                )
                status = "partial"
            except Exception as fallback_exc:
                error_message = str(fallback_exc)
                status = "failed"
        else:
            status = "failed"

    if after_profile is not None:
        after_profile_path = store.save_quote_environment_profile(after_profile)
        try:
            after_draft = build_dealdesk_config_draft(
                after_profile,
                base_config=base_config,
            )
            after_draft_path = store.save_capability_refresh_draft(
                after_draft,
                receipt_id=receipt_id,
                crm_org_id=resolved_crm_org_id,
                org_alias=normalized_org_alias,
            )
        except Exception as exc:
            error_message = str(exc)
            status = "failed"

    receipt = DealDeskCapabilityRefreshReceiptV1(
        receipt_id=receipt_id,
        receipt_path=str(receipt_path),
        created_at=created_at,
        actor_id=str(actor_id or "").strip() or "unknown",
        actor_email=str(actor_email or "").strip() or None,
        crm_org_id=resolved_crm_org_id,
        org_alias=normalized_org_alias,
        status=status,
        error_message=error_message,
        config_base_source=config_base_source,
        config_base_path=config_base_path,
        generated_config_preserved=True,
        existing_generated_config_path=existing_generated_config_path,
        environment_profile_path_before=str(before_profile_path) if before_profile is not None else None,
        environment_profile_path_after=str(after_profile_path) if after_profile_path is not None else None,
        draft_config_path_before=before_draft_path,
        draft_config_path_after=str(after_draft_path) if after_draft_path is not None else None,
        before_environment_profile=before_profile,
        after_environment_profile=after_profile,
        before_draft_config=before_draft,
        after_draft_config=after_draft,
        profile_changes=_payload_diff(
            _profile_payload_for_diff(before_profile),
            _profile_payload_for_diff(after_profile),
        ),
        draft_changes=_payload_diff(
            before_draft.model_dump(exclude_none=False) if before_draft is not None else None,
            after_draft.model_dump(exclude_none=False) if after_draft is not None else None,
        ),
        notes=notes,
    )
    store.save_capability_refresh_receipt(
        receipt,
        crm_org_id=resolved_crm_org_id,
        org_alias=normalized_org_alias,
    )

    if status == "completed":
        message = "Capability refresh completed."
    elif status == "partial":
        message = "Capability refresh completed with warnings."
    else:
        message = "Capability refresh failed."

    return RefreshDealDeskCapabilitiesResultV1(
        status=status,
        message=message,
        receipt=receipt,
    )


def summarize_capability_refresh_receipt(
    receipt: DealDeskCapabilityRefreshReceiptV1 | None,
) -> DealDeskCapabilityRefreshReceiptSummaryV1 | None:
    if receipt is None:
        return None
    return DealDeskCapabilityRefreshReceiptSummaryV1(
        receipt_id=receipt.receipt_id,
        receipt_path=receipt.receipt_path,
        created_at=receipt.created_at,
        actor_id=receipt.actor_id,
        actor_email=receipt.actor_email,
        status=receipt.status,
        error_message=receipt.error_message,
        crm_org_id=receipt.crm_org_id,
        org_alias=receipt.org_alias,
        generated_config_preserved=receipt.generated_config_preserved,
        config_base_source=receipt.config_base_source,
        config_base_path=receipt.config_base_path,
        existing_generated_config_path=receipt.existing_generated_config_path,
        environment_profile_path_after=receipt.environment_profile_path_after,
        draft_config_path_after=receipt.draft_config_path_after,
        profile_change_count=len(receipt.profile_changes),
        draft_change_count=len(receipt.draft_changes),
        notes=list(receipt.notes),
    )


def _payload_diff(
    before: dict[str, Any] | None,
    after: dict[str, Any] | None,
    *,
    prefix: str = "",
) -> list[DealDeskCapabilityRefreshChangeV1]:
    normalized_before = before or {}
    normalized_after = after or {}
    changes: list[DealDeskCapabilityRefreshChangeV1] = []
    keys = sorted(set(normalized_before.keys()) | set(normalized_after.keys()))
    for key in keys:
        path = f"{prefix}.{key}" if prefix else key
        before_value = normalized_before.get(key)
        after_value = normalized_after.get(key)
        if isinstance(before_value, dict) and isinstance(after_value, dict):
            changes.extend(_payload_diff(before_value, after_value, prefix=path))
            continue
        if before_value != after_value:
            changes.append(
                DealDeskCapabilityRefreshChangeV1(
                    path=path,
                    before=before_value,
                    after=after_value,
                )
            )
    return changes


def _profile_payload_for_diff(profile: QuoteEnvironmentProfileV1 | None) -> dict[str, Any] | None:
    if profile is None:
        return None
    payload = profile.to_dict()
    # Refresh receipts should only report semantic capability drift, not the
    # expected timestamp churn from rebuilding the same profile.
    payload.pop("generated_at", None)
    payload.pop("updated_at", None)
    return payload


__all__ = [
    "DealDeskCapabilityRefreshChangeV1",
    "DealDeskCapabilityRefreshReceiptSummaryV1",
    "DealDeskCapabilityRefreshReceiptV1",
    "RefreshDealDeskCapabilitiesRequestV1",
    "RefreshDealDeskCapabilitiesResultV1",
    "refresh_dealdesk_capability_snapshot",
    "summarize_capability_refresh_receipt",
]
