"""Deterministic discovery recommendation builder."""

from __future__ import annotations

from typing import Any

from g_gremlin.autopilot.discovery import (
    FieldMappingSuggestion,
    ObjectProfile,
    ScoutRecommendation,
)
from g_gremlin.autopilot.modules import MODULE_REGISTRY
from g_gremlin.scout.models import ScoutDefinition


class DiscoveryRecommender:
    _PRIORITY_RANK = {"high": 3, "medium": 2, "low": 1}
    _ACTION_RANK = {"configure": 3, "enable": 2, "review": 1}
    _SEVERITY_TO_PRIORITY = {
        "critical": "high",
        "high": "high",
        "medium": "medium",
        "low": "low",
    }

    def __init__(
        self,
        scout_definitions: list[ScoutDefinition],
        object_profiles: list[ObjectProfile],
        field_mapping_suggestions: list[FieldMappingSuggestion],
        scout_findings_summary: dict[str, Any],
        crm: str = "salesforce",
    ) -> None:
        self.scout_definitions = scout_definitions
        self.object_profiles = object_profiles
        self.field_mapping_suggestions = field_mapping_suggestions
        self.scout_findings_summary = scout_findings_summary
        self.crm = (crm or "salesforce").lower()

        self._scout_by_name = {item.name: item for item in scout_definitions}
        self._object_profile_by_name = {item.sobject.lower(): item for item in object_profiles}

    def recommend(self) -> list[ScoutRecommendation]:
        if not self.scout_definitions and not self.field_mapping_suggestions and not self.scout_findings_summary:
            return []

        candidates: list[ScoutRecommendation] = []
        candidates.extend(self._recommend_from_findings())
        candidates.extend(self._recommend_from_field_mappings())
        candidates.extend(self._recommend_missing_scouts())
        candidates.extend(self._recommend_modules())
        return self._dedupe(candidates)

    def _recommend_from_findings(self) -> list[ScoutRecommendation]:
        recommendations: list[ScoutRecommendation] = []
        for scout_name, summary in self.scout_findings_summary.items():
            if str(summary.get("status", "")).lower() != "findings":
                continue
            findings_count = int(summary.get("findings_count", 0) or 0)
            if findings_count <= 0:
                continue
            definition = self._scout_by_name.get(scout_name)
            if definition is None or not definition.parameters:
                continue
            priority = self._priority_from_severity(definition.severity_default)
            recommendations.append(
                ScoutRecommendation(
                    scout_name=scout_name,
                    action="configure",
                    rationale=f"{scout_name} found {findings_count} issues; review parameter tuning",
                    suggested_params=self._parameter_defaults(definition),
                    priority=priority,
                )
            )
        return recommendations

    def _recommend_from_field_mappings(self) -> list[ScoutRecommendation]:
        grouped: dict[str, dict[str, Any]] = {}
        for mapping in self.field_mapping_suggestions:
            if mapping.confidence < 0.8:
                continue
            if mapping.needs_confirmation:
                continue
            target_scout, param_name = self._parse_target(mapping.target)
            if not target_scout or not param_name:
                continue
            definition = self._scout_by_name.get(target_scout)
            if definition is None:
                continue
            parameter = self._parameter_by_name(definition, param_name)
            if parameter is None:
                continue

            entry = grouped.setdefault(
                target_scout,
                {
                    "suggested_params": {},
                    "best_confidence_by_param": {},
                    "rationales": [],
                },
            )

            if self._is_list_parameter(parameter.type):
                values = entry["suggested_params"].setdefault(param_name, [])
                if mapping.source_field not in values:
                    values.append(mapping.source_field)
            else:
                best_confidence = entry["best_confidence_by_param"].get(param_name, -1.0)
                if mapping.confidence >= best_confidence:
                    entry["suggested_params"][param_name] = mapping.source_field
                    entry["best_confidence_by_param"][param_name] = mapping.confidence

            entry["rationales"].append(
                f"{mapping.source_field} -> {mapping.target} ({mapping.confidence:.2f})"
            )

        recommendations: list[ScoutRecommendation] = []
        for scout_name, payload in grouped.items():
            rationales = payload.get("rationales", [])
            if len(rationales) == 1:
                detail = rationales[0]
                left, _sep, right = detail.partition(" (")
                rationale = f"Field mapping confirmed {left} with confidence {right.rstrip(')')}"
            else:
                preview = "; ".join(rationales[:3])
                suffix = "; ..." if len(rationales) > 3 else ""
                rationale = f"Field mappings confirmed: {preview}{suffix}"

            recommendations.append(
                ScoutRecommendation(
                    scout_name=scout_name,
                    action="configure",
                    rationale=rationale,
                    suggested_params=dict(payload.get("suggested_params", {})),
                    priority="high",
                )
            )
        return recommendations

    def _recommend_missing_scouts(self) -> list[ScoutRecommendation]:
        recommendations: list[ScoutRecommendation] = []
        seen = set(self.scout_findings_summary.keys())
        for definition in self.scout_definitions:
            if not definition.enabled:
                continue
            if definition.name in seen:
                continue
            if not self._supports_crm(definition):
                continue
            if not self._sobjects_have_data(definition.sobjects_read):
                continue
            sobjects = ", ".join(definition.sobjects_read)
            recommendations.append(
                ScoutRecommendation(
                    scout_name=definition.name,
                    action="enable",
                    rationale=(
                        f"Scout {definition.name} is available but was not included in the last scan; "
                        f"CRM data for {sobjects} is present"
                    ),
                    suggested_params=self._parameter_defaults(definition),
                    priority="medium",
                )
            )
        return recommendations

    def _recommend_modules(self) -> list[ScoutRecommendation]:
        recommendations: list[ScoutRecommendation] = []

        if "sequence_pause" in MODULE_REGISTRY:
            for scout_name in ("do_not_contact", "sequence_collision", "customer_open_ticket_guardrail"):
                module_rec = self._module_recommendation_from_scout(scout_name, "sequence_pause")
                if module_rec is not None:
                    recommendations.append(module_rec)

        if "slack_routing" in MODULE_REGISTRY:
            for scout_name in ("customer_open_ticket_guardrail", "cross_system_field_drift"):
                module_rec = self._module_recommendation_from_scout(scout_name, "slack_routing")
                if module_rec is not None:
                    recommendations.append(module_rec)

        if "offboarding_cleanup" in MODULE_REGISTRY:
            module_rec = self._module_recommendation_from_scout("inactive_owners", "offboarding_cleanup")
            if module_rec is not None:
                recommendations.append(module_rec)

        if "auto_enrich_missing_fields" in MODULE_REGISTRY:
            module_rec = self._module_recommendation_from_scout(
                "append_candidates",
                "auto_enrich_missing_fields",
            )
            if module_rec is not None:
                recommendations.append(module_rec)

        if "follow_up_enforcement" in MODULE_REGISTRY:
            module_rec = self._module_recommendation_from_scout(
                "meeting_followup_breach",
                "follow_up_enforcement",
            )
            if module_rec is not None:
                recommendations.append(module_rec)

        if "lead_to_account" in MODULE_REGISTRY:
            module_rec = self._module_recommendation_from_scout("unmatched_leads", "lead_to_account")
            if module_rec is not None:
                recommendations.append(module_rec)

        if "deal_desk" in MODULE_REGISTRY:
            dealdesk_hits = [
                (scout_name, summary)
                for scout_name, summary in self.scout_findings_summary.items()
                if scout_name.startswith("dealdesk_")
                and str(summary.get("status", "")).lower() == "findings"
                and int(summary.get("findings_count", 0) or 0) > 0
            ]
            if dealdesk_hits:
                scout_name, summary = max(dealdesk_hits, key=lambda item: int(item[1].get("findings_count", 0) or 0))
                recommendations.append(
                    ScoutRecommendation(
                        scout_name="deal_desk",
                        action="review",
                        rationale=(
                            f"Scout {scout_name} found {int(summary.get('findings_count', 0) or 0)} issues; "
                            "consider enabling the deal_desk module"
                        ),
                        suggested_params={},
                        priority=self._priority_from_scout_name(scout_name),
                    )
                )

        return recommendations

    def _module_recommendation_from_scout(
        self, scout_name: str, module_id: str
    ) -> ScoutRecommendation | None:
        summary = self.scout_findings_summary.get(scout_name)
        if not isinstance(summary, dict):
            return None
        if str(summary.get("status", "")).lower() != "findings":
            return None
        findings_count = int(summary.get("findings_count", 0) or 0)
        if findings_count <= 0:
            return None
        return ScoutRecommendation(
            scout_name=module_id,
            action="review",
            rationale=(
                f"Scout {scout_name} found {findings_count} issues; "
                f"consider enabling the {module_id} module"
            ),
            suggested_params={},
            priority=self._priority_from_scout_name(scout_name),
        )

    def _priority_from_scout_name(self, scout_name: str) -> str:
        definition = self._scout_by_name.get(scout_name)
        if definition is None:
            return "medium"
        return self._priority_from_severity(definition.severity_default)

    def _priority_from_severity(self, severity: str | None) -> str:
        lowered = str(severity or "medium").lower()
        return self._SEVERITY_TO_PRIORITY.get(lowered, "medium")

    @staticmethod
    def _parse_target(target: str) -> tuple[str | None, str | None]:
        if "." not in target:
            return None, None
        scout_name, param_name = target.split(".", 1)
        scout_name = scout_name.strip()
        param_name = param_name.strip()
        if not scout_name or not param_name:
            return None, None
        return scout_name, param_name

    @staticmethod
    def _parameter_by_name(definition: ScoutDefinition, name: str) -> Any | None:
        for parameter in definition.parameters:
            if parameter.name == name:
                return parameter
        return None

    @staticmethod
    def _is_list_parameter(parameter_type: str | None) -> bool:
        return str(parameter_type or "").strip().lower() in {"list", "array"}

    def _supports_crm(self, definition: ScoutDefinition) -> bool:
        supported = definition.supported_crms or []
        if not supported:
            return True
        normalized = {entry.lower() for entry in supported}
        if self.crm == "salesforce":
            return "salesforce" in normalized or "sfdc" in normalized
        return self.crm in normalized

    def _sobjects_have_data(self, sobjects: list[str]) -> bool:
        if not sobjects:
            return False
        for sobject in sobjects:
            profile = self._object_profile_by_name.get(sobject.lower())
            if profile is None:
                return False
            if int(profile.record_count) <= 0:
                return False
        return True

    @staticmethod
    def _parameter_defaults(definition: ScoutDefinition) -> dict[str, Any]:
        return {parameter.name: parameter.default for parameter in definition.parameters}

    def _dedupe(self, recommendations: list[ScoutRecommendation]) -> list[ScoutRecommendation]:
        best: dict[str, ScoutRecommendation] = {}
        for candidate in recommendations:
            existing = best.get(candidate.scout_name)
            if existing is None or self._is_candidate_better(candidate, existing):
                best[candidate.scout_name] = candidate
        return sorted(best.values(), key=lambda item: item.scout_name)

    def _is_candidate_better(
        self, candidate: ScoutRecommendation, existing: ScoutRecommendation
    ) -> bool:
        candidate_priority = self._PRIORITY_RANK.get(candidate.priority, 0)
        existing_priority = self._PRIORITY_RANK.get(existing.priority, 0)
        if candidate_priority != existing_priority:
            return candidate_priority > existing_priority

        candidate_action = self._ACTION_RANK.get(candidate.action, 0)
        existing_action = self._ACTION_RANK.get(existing.action, 0)
        return candidate_action > existing_action


__all__ = ["DiscoveryRecommender"]
