"""Workflow execution mode for use case orchestration."""

from __future__ import annotations

import csv
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any

from kiessclaw.codegen.scaffold import build_usecase_manifest
from kiessclaw.integrations import SlackIntegration
from kiessclaw.models.account import Account
from kiessclaw.models.contact import Contact
from kiessclaw.prompting.prompt_factory import render_prompt_pack
from kiessclaw.providers import active_provider
from kiessclaw.runtime import workspace_memory
from kiessclaw.usecases.registry import get_usecase


@dataclass(slots=True)
class WorkflowResult:
    """Normalized workflow execution result payload."""

    usecase_id: str
    steps_run: list[str] = field(default_factory=list)
    outputs: dict[str, Any] = field(default_factory=dict)
    contacts_created: int = 0
    enrolled: int = 0
    dry_run: bool = True
    errors: list[str] = field(default_factory=list)

    def to_dict(self) -> dict[str, Any]:
        """Return JSON-serializable result payload."""
        return asdict(self)


class WorkflowRunner:
    """Execute use case workflows against the shared runtime."""

    def __init__(self, config: dict[str, Any], registry: dict[str, Any]):
        """Initialize runner with runtime config and loaded agents."""
        self.config = config
        self.registry = registry
        self.memory = workspace_memory(config)
        slack_cfg = config.get("slack", {}) if isinstance(config, dict) else {}
        slack_enabled = bool(slack_cfg.get("enabled", False)) if isinstance(slack_cfg, dict) else False
        self.slack = SlackIntegration(
            webhook_url=slack_cfg.get("webhook_url") if slack_enabled else None,
            bot_token=slack_cfg.get("bot_token") if isinstance(slack_cfg, dict) else None,
        )

    def run(
        self,
        usecase_id: str,
        inputs: dict[str, Any],
        dry_run: bool = True,
        auto_enroll: bool = False,
        provider_name: str | None = None,
    ) -> WorkflowResult:
        """Run one use case workflow end-to-end."""
        result = WorkflowResult(usecase_id=usecase_id, dry_run=dry_run)
        state_snapshot = self._snapshot_data() if dry_run else {}
        try:
            if usecase_id == "outreach_sdr":
                self._run_outreach_sdr(result, inputs=inputs, dry_run=dry_run, auto_enroll=auto_enroll)
            elif usecase_id == "seo_audit_to_leads":
                self._run_seo_audit_to_leads(result, inputs=inputs)
            elif usecase_id == "customer_success_recovery":
                self._run_customer_success_recovery(result, inputs=inputs, dry_run=dry_run)
            elif usecase_id == "enrichment_only":
                self._run_enrichment_only(
                    result,
                    inputs=inputs,
                    dry_run=dry_run,
                    provider_name=provider_name,
                )
            elif usecase_id in {"github_oss_launch", "client_onboarding"}:
                self._run_generation_only(result, inputs=inputs)
            else:
                result.errors.append(f"Unknown use case: {usecase_id}")
        except Exception as exc:  # noqa: BLE001
            result.errors.append(str(exc))
        finally:
            if dry_run:
                self._restore_data(state_snapshot)
            self.slack.notify(
                "workflow_complete",
                {
                    "usecase_id": usecase_id,
                    "contacts_created": result.contacts_created,
                    "enrolled": result.enrolled,
                    "errors": list(result.errors),
                },
            )
        return result

    def _run_outreach_sdr(
        self,
        result: WorkflowResult,
        inputs: dict[str, Any],
        dry_run: bool,
        auto_enroll: bool,
    ) -> None:
        """Run outreach SDR workflow with qualification and optional enrollment."""
        result.steps_run.append("validate_inputs")
        company = str(inputs.get("company") or "").strip()
        domain = str(inputs.get("domain") or "").strip().lower()
        if not company or not domain:
            result.errors.append("Missing required inputs: company, domain")
            return

        contacts_input = self._collect_contacts(inputs)
        if not contacts_input:
            result.errors.append("No contacts provided (contacts list or contacts_csv)")
            return

        kpro = self.registry.get("KPRO")
        kseq = self.registry.get("KSEQ")
        if kpro is None or kseq is None:
            result.errors.append("Required agents KPRO and KSEQ are not available")
            return

        result.steps_run.append("score_contacts")
        qualified_contacts: list[dict[str, Any]] = []
        scored_rows: list[dict[str, Any]] = []
        for row in contacts_input:
            email = str(row.get("email") or "").strip().lower()
            if not email:
                continue
            score = kpro.prospect_skill.score_icp_fit(  # type: ignore[attr-defined]
                {
                    "title": row.get("title"),
                    "industry": row.get("industry"),
                    "employee_count": row.get("employee_count"),
                    "email": email,
                }
            )
            scored_row = dict(row)
            scored_row["email"] = email
            scored_row["icp_score"] = int(score["score"])
            scored_row["qualified"] = bool(score["qualified"])
            scored_rows.append(scored_row)
            if scored_row["qualified"]:
                qualified_contacts.append(scored_row)

        result.outputs["scored_contacts"] = scored_rows
        if dry_run or (not auto_enroll and bool(inputs.get("require_auto_enroll", False))):
            return

        result.steps_run.append("enroll_contacts")
        sequence_id = self._ensure_sequence(
            name=str(inputs.get("sequence_name") or "Workflow Outreach SDR"),
            sender_email=str(inputs.get("sender_email") or self.config.get("email", {}).get("smtp", {}).get("username") or ""),
            subject=str(inputs.get("subject") or "Quick idea for {{company}}"),
            body=str(inputs.get("body") or "Hi {{first_name}},\n\nCan I share one idea for {{company}}?"),
        )

        providers_cfg = self.config.get("providers", {})
        configured_crm = str(providers_cfg.get("crm", "mock")).strip().lower() if isinstance(providers_cfg, dict) else "mock"
        crm_provider: Any | None = None
        if configured_crm in {"", "none", "mock"}:
            result.outputs["crm_sync"] = {
                "provider": configured_crm or "mock",
                "attempted": 0,
                "synced": 0,
                "skipped": True,
            }
        else:
            crm_provider = active_provider(kind="crm", config=self.config)
            if crm_provider is None or crm_provider.name in {"none", "mock"}:
                result.outputs["crm_sync"] = {
                    "provider": crm_provider.name if crm_provider else "none",
                    "attempted": 0,
                    "synced": 0,
                    "skipped": True,
                    "warning": "CRM provider unavailable or running in mock fallback mode",
                }
                crm_provider = None

        enrolled_count = 0
        created_count = 0
        crm_sync_results: list[dict[str, Any]] = []
        for row in qualified_contacts:
            contact_id, created = self._ensure_contact(
                email=row["email"],
                domain=domain,
                company=company,
                first_name=str(row.get("first_name") or ""),
                last_name=str(row.get("last_name") or ""),
                title=str(row.get("title") or ""),
                icp_score=int(row.get("icp_score") or 0),
            )
            created_count += 1 if created else 0
            if self._enroll_contact(contact_id=contact_id, sequence_id=sequence_id):
                enrolled_count += 1
            if crm_provider is not None:
                crm_sync_results.append(self._sync_contact_to_crm(crm_provider, contact_id))

        result.contacts_created = created_count
        result.enrolled = enrolled_count
        result.outputs["sequence_id"] = sequence_id
        if crm_provider is not None:
            warnings = [
                item["error"] for item in crm_sync_results if (not item.get("ok")) and isinstance(item.get("error"), str)
            ]
            result.outputs["crm_sync"] = {
                "provider": crm_provider.name,
                "attempted": len(crm_sync_results),
                "synced": sum(1 for item in crm_sync_results if item.get("ok")),
                "skipped": False,
                "results": crm_sync_results,
                "warnings": warnings,
            }

    def _run_seo_audit_to_leads(self, result: WorkflowResult, inputs: dict[str, Any]) -> None:
        """Run SEO audit and return non-sending lead insights."""
        result.steps_run.append("validate_inputs")
        domain = str(inputs.get("domain") or "").strip()
        if not domain:
            result.errors.append("Missing required input: domain")
            return
        krawl = self.registry.get("KRAWL")
        if krawl is None:
            result.errors.append("Required agent KRAWL is not available")
            return
        result.steps_run.append("audit")
        url = domain if domain.startswith(("http://", "https://")) else f"https://{domain}"
        audit = krawl.run("audit", url=url, return_data=True)
        if not isinstance(audit, dict):
            result.errors.append("KRAWL audit returned invalid payload")
            return
        result.steps_run.append("lead_plan")
        leads = []
        owner = audit.get("owner_contact")
        if owner:
            leads.append(
                {
                    "email": owner,
                    "domain": audit.get("domain"),
                    "priority": "high" if int(audit.get("technical_score", 100)) < 60 else "medium",
                }
            )
        result.outputs["audit"] = audit
        result.outputs["lead_list"] = leads

    def _run_customer_success_recovery(
        self,
        result: WorkflowResult,
        inputs: dict[str, Any],
        dry_run: bool,
    ) -> None:
        """Run churn risk scoring workflow and optional recovery enrollment."""
        result.steps_run.append("validate_inputs")
        threshold = int(inputs.get("last_active_days_threshold", 30))
        contacts_input = self._collect_contacts(inputs)
        if not contacts_input:
            result.errors.append("No contacts provided (contacts list or contacts_csv)")
            return
        if dry_run:
            result.steps_run.append("score_churn_risk")
            flagged = [row for row in contacts_input if int(row.get("last_active_days", 0)) > threshold]
            result.outputs["qualified_contacts"] = flagged
            return

        result.steps_run.extend(["score_churn_risk", "enroll_recovery"])
        sequence_id = self._ensure_sequence(
            name=str(inputs.get("sequence_name") or "Customer Recovery"),
            sender_email=str(inputs.get("sender_email") or self.config.get("email", {}).get("smtp", {}).get("username") or ""),
            subject=str(inputs.get("subject") or "Quick check-in for {{company}}"),
            body=str(inputs.get("body") or "Hi {{first_name}},\n\nWanted to help you get value from {{company}} again."),
        )
        enrolled = 0
        created = 0
        for row in contacts_input:
            if int(row.get("last_active_days", 0)) <= threshold:
                continue
            contact_id, was_created = self._ensure_contact(
                email=str(row.get("email") or "").lower(),
                domain=str(row.get("domain") or ""),
                company=str(row.get("company") or row.get("domain") or ""),
                first_name=str(row.get("first_name") or ""),
                last_name=str(row.get("last_name") or ""),
                title=str(row.get("title") or ""),
            )
            created += 1 if was_created else 0
            if self._enroll_contact(contact_id, sequence_id):
                enrolled += 1
        result.contacts_created = created
        result.enrolled = enrolled
        result.outputs["sequence_id"] = sequence_id

    def _run_enrichment_only(
        self,
        result: WorkflowResult,
        inputs: dict[str, Any],
        dry_run: bool,
        provider_name: str | None,
    ) -> None:
        """Run enrichment-only workflow with pluggable providers."""
        result.steps_run.append("validate_inputs")
        contacts_input = self._collect_contacts(inputs)
        if not contacts_input:
            result.errors.append("No contacts provided (contacts list or contacts_csv)")
            return

        provider = active_provider("enrichment", config=self.config, preferred_name=provider_name)
        if provider is None:
            result.errors.append("No enrichment provider available")
            return
        result.steps_run.append("enrich_contacts")

        enriched_rows = []
        for row in contacts_input:
            email = str(row.get("email") or "").strip().lower()
            if not email:
                continue
            enriched_rows.append(provider.enrich_contact(email).data | {"provider": provider.name})

        result.outputs["provider"] = provider.name
        result.outputs["enriched_contacts"] = enriched_rows

        if dry_run:
            return

        existing = self.memory.load_data("enriched_contacts")
        existing.extend(enriched_rows)
        self.memory.save_data("enriched_contacts", existing)

    def _run_generation_only(self, result: WorkflowResult, inputs: dict[str, Any]) -> None:
        """Run prompt+scaffold generation-only use cases."""
        spec = get_usecase(result.usecase_id)
        result.steps_run.extend(["render_prompt_pack", "build_scaffold_manifest"])
        result.outputs["prompt_pack"] = render_prompt_pack(result.usecase_id, inputs=inputs, deterministic=True)
        result.outputs["scaffold_manifest"] = build_usecase_manifest(result.usecase_id, inputs=inputs)
        result.outputs["required_inputs"] = list(spec.required_inputs)

    def _collect_contacts(self, inputs: dict[str, Any]) -> list[dict[str, Any]]:
        """Collect contacts from inline payload or CSV path."""
        contacts = inputs.get("contacts")
        if isinstance(contacts, list):
            return [item for item in contacts if isinstance(item, dict)]

        csv_path = inputs.get("contacts_csv")
        if not isinstance(csv_path, str) or not csv_path.strip():
            return []
        path = Path(csv_path)
        if not path.exists():
            return []

        rows: list[dict[str, Any]] = []
        with path.open(newline="", encoding="utf-8-sig") as handle:
            reader = csv.DictReader(handle)
            for row in reader:
                rows.append({str(k): v for k, v in row.items()})
        return rows

    def _ensure_contact(
        self,
        email: str,
        domain: str,
        company: str,
        first_name: str,
        last_name: str,
        title: str,
        icp_score: int = 0,
    ) -> tuple[str, bool]:
        """Create or reuse contact + account and return contact id + creation flag."""
        contacts = self.memory.load_data("contacts")
        existing = next((item for item in contacts if item.get("email") == email), None)
        if existing:
            return str(existing["id"]), False

        accounts = self.memory.load_data("accounts")
        existing_account = next((item for item in accounts if item.get("domain") == domain), None)
        if existing_account is None:
            account = Account(domain=domain, name=company or domain)
            existing_account = account.to_dict()
            accounts.append(existing_account)
            self.memory.save_data("accounts", accounts)

        contact = Contact(
            account_id=str(existing_account["id"]),
            email=email,
            first_name=first_name,
            last_name=last_name,
            title=title or None,
            icp_score=icp_score,
        )
        contacts.append(contact.to_dict())
        self.memory.save_data("contacts", contacts)
        return contact.id, True

    def _ensure_sequence(self, name: str, sender_email: str, subject: str, body: str) -> str:
        """Create or reuse active sequence for workflow enrollments."""
        kseq = self.registry["KSEQ"]
        sequences = self.memory.load_data("sequences")
        existing = next((item for item in sequences if item.get("name") == name), None)
        if existing is None:
            sequence = kseq.sequence_skill.create_sequence(  # type: ignore[attr-defined]
                name=name,
                sender_email=sender_email,
                steps=[{"channel": "email", "delay_days": 0, "subject": subject, "body": body}],
            )
            sequence["status"] = "active"
            sequences.append(sequence)
            self.memory.save_data("sequences", sequences)
            return str(sequence["id"])

        if existing.get("status") != "active":
            existing["status"] = "active"
            self.memory.save_data("sequences", sequences)
        return str(existing["id"])

    def _enroll_contact(self, contact_id: str, sequence_id: str) -> bool:
        """Enroll contact if active enrollment does not already exist."""
        enrollments = self.memory.load_data("enrollments")
        existing = next(
            (
                item
                for item in enrollments
                if item.get("contact_id") == contact_id
                and item.get("sequence_id") == sequence_id
                and item.get("status") in {"active", "paused", "completed", "replied"}
            ),
            None,
        )
        if existing:
            return False

        kseq = self.registry["KSEQ"]
        enrollment = kseq.sequence_skill.enroll_contact(  # type: ignore[attr-defined]
            sequence_id=sequence_id,
            contact_id=contact_id,
            start_immediately=True,
        )
        enrollments.append(enrollment)
        self.memory.save_data("enrollments", enrollments)
        return True

    def _snapshot_data(self) -> dict[str, str]:
        """Snapshot workspace data files for dry-run rollback."""
        snapshot: dict[str, str] = {}
        for path in sorted(self.memory.data_dir.glob("*.json")):
            snapshot[path.name] = path.read_text(encoding="utf-8")
        return snapshot

    def _restore_data(self, snapshot: dict[str, str]) -> None:
        """Restore workspace data files from dry-run snapshot."""
        for name, content in snapshot.items():
            (self.memory.data_dir / name).write_text(content, encoding="utf-8")

    def _contact_by_id(self, contact_id: str) -> dict[str, Any] | None:
        """Return one contact record by ID."""
        contacts = self.memory.load_data("contacts")
        return next((item for item in contacts if item.get("id") == contact_id), None)

    def _sync_contact_to_crm(self, provider: Any, contact_id: str) -> dict[str, Any]:
        """Sync one qualified contact to the active CRM provider."""
        record = self._contact_by_id(contact_id)
        if record is None:
            return {
                "contact_id": contact_id,
                "provider": getattr(provider, "name", "unknown"),
                "ok": False,
                "error": "Contact record not found for CRM sync",
            }

        sync_fn = getattr(provider, "create_contact", None)
        if not callable(sync_fn):
            return {
                "contact_id": contact_id,
                "email": record.get("email"),
                "provider": getattr(provider, "name", "unknown"),
                "ok": False,
                "error": "Provider does not implement create_contact",
            }

        try:
            result = sync_fn(Contact.from_dict(record))
            return {
                "contact_id": contact_id,
                "email": record.get("email"),
                "provider": result.provider,
                "ok": result.ok,
                "error": result.error,
            }
        except Exception as exc:  # noqa: BLE001
            return {
                "contact_id": contact_id,
                "email": record.get("email"),
                "provider": getattr(provider, "name", "unknown"),
                "ok": False,
                "error": str(exc),
            }
