"""Actions applied to Scout findings."""

from __future__ import annotations

import csv
from pathlib import Path
from typing import Any, Dict, List, Tuple

from .crm_provider import CRMProvider
from .models import EvidenceRow, ScoutFinding
from .providers import create_provider


DEFAULT_FLAG_FIELD = "Needs_Review__c"
DEFAULT_TASK_STATUS = "Not Started"
DEFAULT_TASK_PRIORITY = "Normal"


def _write_unsupported_message(provider_name: str) -> str:
    if provider_name == "hubspot":
        return "Write actions not supported for HubSpot yet."
    if provider_name == "dynamics":
        return "Write actions not supported for Dynamics yet."
    return "Write actions not supported for this CRM."


def export_csv(finding: ScoutFinding, output_path: Path) -> Path:
    output_path.parent.mkdir(parents=True, exist_ok=True)
    fieldnames = ["record_id", "sobject", "reason_code", "reason", "severity", "related_ids"]
    extra_fields: List[str] = []
    for row in finding.evidence:
        for key in row.fields.keys():
            if key not in fieldnames and key not in extra_fields:
                extra_fields.append(key)
    fieldnames.extend(extra_fields)

    with output_path.open("w", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=fieldnames)
        writer.writeheader()
        for row in finding.evidence:
            payload: Dict[str, Any] = {
                "record_id": row.record_id,
                "sobject": row.sobject,
                "reason_code": row.reason_code,
                "reason": row.reason,
                "severity": row.severity,
                "related_ids": ";".join(row.related_ids) if row.related_ids else "",
            }
            payload.update(row.fields)
            writer.writerow(payload)
    return output_path


def flag_records(
    finding: ScoutFinding,
    field_name: str | None = None,
    *,
    org_alias: str | None = None,
    apply: bool = False,
    provider: CRMProvider | None = None,
    crm: str = "salesforce",
    hubspot_profile: str | None = None,
    dynamics_profile: str | None = None,
) -> Dict[str, Any]:
    field = field_name or DEFAULT_FLAG_FIELD
    updates: List[Dict[str, Any]] = []
    failed: List[Dict[str, Any]] = []
    active_provider = provider or create_provider(
        crm,
        org_alias=org_alias,
        hubspot_profile=hubspot_profile,
        dynamics_profile=dynamics_profile,
    )
    if apply and getattr(active_provider, "name", "") in {"hubspot", "dynamics"}:
        msg = _write_unsupported_message(getattr(active_provider, "name", ""))
        return {
            "action": "flag_record",
            "field": field,
            "applied": apply,
            "updated": [],
            "failed": [
                {
                    "record_id": "",
                    "sobject": "",
                    "error": msg,
                }
            ],
        }

    for row in finding.evidence:
        if not row.record_id or not row.sobject:
            continue
        values = {field: True}
        if apply:
            try:
                resp = active_provider.update_record(row.sobject, row.record_id, values)
                updates.append({"record_id": row.record_id, "sobject": row.sobject, "result": resp})
            except Exception as exc:
                failed.append({"record_id": row.record_id, "sobject": row.sobject, "error": str(exc)})
        else:
            updates.append({"record_id": row.record_id, "sobject": row.sobject, "values": values})

    return {
        "action": "flag_record",
        "field": field,
        "applied": apply,
        "updated": updates,
        "failed": failed,
    }


def _task_target_fields(sobject: str, record_id: str) -> Dict[str, str]:
    if sobject.lower() in ("lead", "contact"):
        return {"WhoId": record_id}
    return {"WhatId": record_id}


def _render_subject(template: str, row) -> str:
    mapping = {
        "record_id": row.record_id,
        "reason": row.reason,
        "reason_code": row.reason_code,
        "sobject": row.sobject,
    }
    try:
        return template.format_map(mapping)
    except Exception:
        return template


def create_tasks(
    finding: ScoutFinding,
    subject_template: str,
    *,
    org_alias: str | None = None,
    apply: bool = False,
    provider: CRMProvider | None = None,
    crm: str = "salesforce",
    hubspot_profile: str | None = None,
    dynamics_profile: str | None = None,
) -> Dict[str, Any]:
    created: List[Dict[str, Any]] = []
    failed: List[Dict[str, Any]] = []
    active_provider = provider or create_provider(
        crm,
        org_alias=org_alias,
        hubspot_profile=hubspot_profile,
        dynamics_profile=dynamics_profile,
    )
    if apply and getattr(active_provider, "name", "") in {"hubspot", "dynamics"}:
        msg = _write_unsupported_message(getattr(active_provider, "name", ""))
        return {
            "action": "create_task",
            "subject_template": subject_template,
            "applied": apply,
            "created": [],
            "failed": [
                {"record_id": "", "sobject": "", "error": msg}
            ],
        }

    for row in finding.evidence:
        if not row.record_id or not row.sobject:
            continue
        values: Dict[str, Any] = {
            "Subject": _render_subject(subject_template, row),
            "Status": DEFAULT_TASK_STATUS,
            "Priority": DEFAULT_TASK_PRIORITY,
        }
        values.update(_task_target_fields(row.sobject, row.record_id))
        if apply:
            try:
                resp = active_provider.create_record("task", values)
                created.append({"record_id": row.record_id, "sobject": row.sobject, "result": resp})
            except Exception as exc:
                failed.append({"record_id": row.record_id, "sobject": row.sobject, "error": str(exc)})
        else:
            created.append({"record_id": row.record_id, "sobject": row.sobject, "values": values})

    return {
        "action": "create_task",
        "subject_template": subject_template,
        "applied": apply,
        "created": created,
        "failed": failed,
    }

def _confidence_rank(value: str) -> int:
    mapping = {"low": 0, "medium": 1, "high": 2}
    return mapping.get(value.lower(), 1)


def export_merge_worksheet(
    finding: ScoutFinding,
    output_path: Path,
    confidence_threshold: str = "medium",
) -> Path:
    output_path.parent.mkdir(parents=True, exist_ok=True)
    threshold_rank = _confidence_rank(confidence_threshold)
    rows: Dict[Tuple[str, str], Dict[str, Any]] = {}

    for row in finding.evidence:
        cluster_id = str(row.fields.get("cluster_id", "") or "")
        if not cluster_id:
            continue
        confidence = str(row.fields.get("confidence", "low") or "low")
        if _confidence_rank(confidence) < threshold_rank:
            continue
        key = (cluster_id, row.record_id)
        payload = {
            "cluster_id": cluster_id,
            "record_id": row.record_id,
            "sobject": row.sobject,
            "is_survivor": bool(row.fields.get("is_recommended_survivor")),
            "confidence": confidence,
            "domain": row.fields.get("domain", ""),
            "Name": row.fields.get("Name", ""),
            "Company": row.fields.get("Company", ""),
            "Email": row.fields.get("Email", ""),
            "Website": row.fields.get("Website", ""),
        }
        existing = rows.get(key)
        if not existing:
            rows[key] = payload
            continue
        # Prefer higher confidence, then survivor flag.
        if _confidence_rank(confidence) > _confidence_rank(existing.get("confidence", "low")):
            rows[key] = payload
        elif payload["is_survivor"] and not existing.get("is_survivor"):
            rows[key] = payload

    fieldnames = [
        "cluster_id",
        "record_id",
        "sobject",
        "is_survivor",
        "confidence",
        "domain",
        "Name",
        "Company",
        "Email",
        "Website",
    ]
    with output_path.open("w", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=fieldnames)
        writer.writeheader()
        for payload in sorted(rows.values(), key=lambda r: (r["cluster_id"], r["record_id"])):
            writer.writerow(payload)

    return output_path


def create_cluster_tasks(
    finding: ScoutFinding,
    subject_template: str = "Dedupe review: cluster {cluster_id}",
    assignee_id: str | None = None,
    *,
    org_alias: str | None = None,
    apply: bool = False,
    provider: CRMProvider | None = None,
    crm: str = "salesforce",
    hubspot_profile: str | None = None,
    dynamics_profile: str | None = None,
) -> Dict[str, Any]:
    created: List[Dict[str, Any]] = []
    failed: List[Dict[str, Any]] = []
    active_provider = provider
    if apply and active_provider is None:
        active_provider = create_provider(
            crm,
            org_alias=org_alias,
            hubspot_profile=hubspot_profile,
            dynamics_profile=dynamics_profile,
        )
    if apply and getattr(active_provider, "name", "") in {"hubspot", "dynamics"}:
        msg = _write_unsupported_message(getattr(active_provider, "name", ""))
        return {
            "action": "create_cluster_tasks",
            "subject_template": subject_template,
            "applied": apply,
            "created": [],
            "failed": [{"cluster_id": "", "error": msg}],
        }

    clusters: Dict[str, List[EvidenceRow]] = {}
    for row in finding.evidence:
        cluster_id = str(row.fields.get("cluster_id", "") or "")
        if not cluster_id or not row.record_id:
            continue
        clusters.setdefault(cluster_id, []).append(row)

    for cluster_id, rows in clusters.items():
        # Deduplicate record ids
        by_record: Dict[str, EvidenceRow] = {}
        for row in rows:
            if row.record_id not in by_record:
                by_record[row.record_id] = row
        deduped = list(by_record.values())

        survivor = next((r for r in deduped if r.fields.get("is_recommended_survivor")), None)
        if survivor is None and deduped:
            survivor = deduped[0]
        if survivor is None:
            continue

        names = []
        for r in deduped:
            label = r.fields.get("Name") or r.fields.get("Company") or r.record_id
            names.append(f"{r.record_id}: {label}")
        description = "Records:\n" + "\n".join(names)

        values: Dict[str, Any] = {
            "Subject": subject_template.format(cluster_id=cluster_id),
            "Status": DEFAULT_TASK_STATUS,
            "Priority": DEFAULT_TASK_PRIORITY,
            "Description": description,
        }
        values.update(_task_target_fields(survivor.sobject, survivor.record_id))
        if assignee_id:
            values["OwnerId"] = assignee_id

        if apply:
            try:
                if active_provider is None:
                    raise RuntimeError("Provider not available for apply")
                resp = active_provider.create_record("task", values)
                created.append({"cluster_id": cluster_id, "result": resp})
            except Exception as exc:
                failed.append({"cluster_id": cluster_id, "error": str(exc)})
        else:
            created.append({"cluster_id": cluster_id, "values": values})

    return {
        "action": "create_cluster_tasks",
        "subject_template": subject_template,
        "applied": apply,
        "created": created,
        "failed": failed,
    }


def flag_cluster_records(
    finding: ScoutFinding,
    field_name: str = "Needs_Dedupe_Review__c",
    *,
    org_alias: str | None = None,
    apply: bool = False,
    provider: CRMProvider | None = None,
    crm: str = "salesforce",
    hubspot_profile: str | None = None,
    dynamics_profile: str | None = None,
) -> Dict[str, Any]:
    updates: List[Dict[str, Any]] = []
    failed: List[Dict[str, Any]] = []
    active_provider = provider
    if apply and active_provider is None:
        active_provider = create_provider(
            crm,
            org_alias=org_alias,
            hubspot_profile=hubspot_profile,
            dynamics_profile=dynamics_profile,
        )
    if apply and getattr(active_provider, "name", "") in {"hubspot", "dynamics"}:
        msg = _write_unsupported_message(getattr(active_provider, "name", ""))
        return {
            "action": "flag_cluster_records",
            "field": field_name,
            "applied": apply,
            "updated": [],
            "failed": [{"record_id": "", "sobject": "", "error": msg}],
        }

    seen: set[tuple[str, str]] = set()
    for row in finding.evidence:
        if not row.record_id or not row.sobject:
            continue
        key = (row.sobject, row.record_id)
        if key in seen:
            continue
        seen.add(key)
        values = {field_name: True}
        if apply:
            try:
                if active_provider is None:
                    raise RuntimeError("Provider not available for apply")
                resp = active_provider.update_record(row.sobject, row.record_id, values)
                updates.append({"record_id": row.record_id, "sobject": row.sobject, "result": resp})
            except Exception as exc:
                failed.append({"record_id": row.record_id, "sobject": row.sobject, "error": str(exc)})
        else:
            updates.append({"record_id": row.record_id, "sobject": row.sobject, "values": values})

    return {
        "action": "flag_cluster_records",
        "field": field_name,
        "applied": apply,
        "updated": updates,
        "failed": failed,
    }


__all__ = [
    "export_csv",
    "flag_records",
    "create_tasks",
    "create_cluster_tasks",
    "flag_cluster_records",
    "export_merge_worksheet",
    "DEFAULT_FLAG_FIELD",
]
