"""Autonomous SEO -> SDR lead generation pipeline."""

from __future__ import annotations

import logging
import json
from datetime import datetime, timezone
from typing import Any
from urllib import error as urllib_error
from urllib import request as urllib_request
from urllib.parse import urlparse

try:
    import requests
except ImportError:  # pragma: no cover - fallback path used in restricted envs
    requests = None

from kiessclaw.models.account import Account
from kiessclaw.models.contact import Contact
from kiessclaw.integrations import SlackIntegration

logger = logging.getLogger(__name__)


class LeadGenPipeline:
    """Orchestrate SEO audits into SDR enrollment with optional webhook fan-out."""

    def __init__(self, config: dict[str, Any], registry: dict[str, Any]):
        """Initialize pipeline with loaded agent registry and runtime config."""
        self.config = config
        self.registry = registry
        self.krawl = registry.get("KRAWL")
        self.kpro = registry.get("KPRO")
        self.kseq = registry.get("KSEQ")

        if not self.krawl:
            raise RuntimeError("KRAWL agent is required for LeadGenPipeline")
        if not self.kpro:
            raise RuntimeError("KPRO agent is required for LeadGenPipeline")
        if not self.kseq:
            raise RuntimeError("KSEQ agent is required for LeadGenPipeline")

        self.memory = self.kseq.memory
        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_audit(self, url: str, auto_enroll: bool = False, max_pages: int | None = None) -> dict[str, Any]:
        """Run SEO audit and optionally auto-route low-score domains into SDR workflow."""
        audit = self.krawl.run("audit", url=url, max_pages=max_pages, return_data=True)
        if not isinstance(audit, dict):
            raise RuntimeError("KRAWL audit did not return structured data")

        domain = audit.get("domain") or self._extract_domain(url)
        seo_score = int(audit.get("technical_score", 0))
        contact_email = audit.get("owner_contact")

        pipeline_result: dict[str, Any] = {
            "domain": domain,
            "seo_score": seo_score,
            "icp_score": None,
            "contact_email": contact_email,
            "qualified": False,
            "enrolled": False,
            "sequence_id": None,
            "contact_id": None,
            "timestamp": datetime.now(timezone.utc).isoformat(),
        }

        if auto_enroll and seo_score < 60:
            icp_score = self._score_domain(domain=domain, contact_email=contact_email, seo_score=seo_score)
            pipeline_result["icp_score"] = icp_score
            if icp_score >= 60 and contact_email:
                contact_id = self._ensure_contact(domain=domain, email=contact_email, seo_score=seo_score)
                sequence_id = self._ensure_default_sequence()
                enrolled = self._enroll_contact(contact_id=contact_id, sequence_id=sequence_id)
                pipeline_result.update(
                    {
                        "qualified": True,
                        "enrolled": enrolled,
                        "sequence_id": sequence_id,
                        "contact_id": contact_id,
                    }
                )
                if enrolled:
                    self.slack.notify(
                        "qualified_lead",
                        {
                            "contact": contact_email,
                            "icp_score": icp_score,
                            "seo_score": seo_score,
                            "sequence_id": sequence_id,
                            "workflow": "seo_audit_to_leads",
                        },
                    )
                self._post_klytics_hook(pipeline_result)
            elif icp_score >= 60:
                pipeline_result["qualified"] = True

        self._log_lead(pipeline_result)
        return {"audit": audit, "pipeline": pipeline_result}

    def _score_domain(self, domain: str, contact_email: str | None, seo_score: int) -> int:
        """Score domain fit with KPRO logic plus low-score SEO urgency signal."""
        title_guess = self._infer_contact_title(contact_email)
        base_result = self.kpro.prospect_skill.score_icp_fit(  # type: ignore[attr-defined]
            {
                "title": title_guess,
                "industry": "software",
                "employee_count": 80,
                "email": contact_email or f"info@{domain}",
            }
        )
        urgency_boost = max(0, min(15, 60 - seo_score))
        return min(100, int(base_result["score"]) + urgency_boost)

    def _ensure_contact(self, domain: str, email: str, seo_score: int) -> str:
        """Create or reuse an SDR contact for the audited domain."""
        contacts = self.memory.load_data("contacts")
        existing = next((item for item in contacts if item.get("email") == email), None)
        if existing:
            return existing["id"]

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

        first_name = email.split("@")[0].split(".")[0].replace("_", " ").title()
        contact = Contact(
            account_id=account["id"],
            email=email,
            first_name=first_name,
            title=self._infer_contact_title(email),
            tags=["seo_low_score", f"seo_score_{seo_score}"],
        )
        contacts.append(contact.to_dict())
        self.memory.save_data("contacts", contacts)
        return contact.id

    def _ensure_default_sequence(self) -> str:
        """Create or reuse the default SEO recovery outreach sequence."""
        name = self.config.get("pipeline", {}).get("default_sequence_name", "SEO Recovery Outreach")
        sender_email = (
            self.config.get("pipeline", {}).get("default_sender_email")
            or self.config.get("email", {}).get("smtp", {}).get("username")
            or "outreach@kiessclaw.dev"
        )
        sequences = self.memory.load_data("sequences")
        sequence = next((item for item in sequences if item.get("name") == name), None)

        if not sequence:
            sequence = self.kseq.sequence_skill.create_sequence(  # type: ignore[attr-defined]
                name=name,
                sender_email=sender_email,
                steps=[
                    {
                        "channel": "email",
                        "delay_days": 0,
                        "subject": "Quick SEO fix idea for {{company}}",
                        "body": (
                            "Hi {{first_name}},\n\n"
                            "We just identified technical SEO issues likely hurting {{company}} discoverability. "
                            "Would you like a short recovery plan?"
                        ),
                    }
                ],
            )
            sequence["status"] = "active"
            sequences.append(sequence)
            self.memory.save_data("sequences", sequences)
            return sequence["id"]

        if not sequence.get("steps"):
            step = self.kseq.sequence_skill.add_step(  # type: ignore[attr-defined]
                sequence=sequence,
                channel="email",
                delay_days=0,
                subject="Quick SEO fix idea for {{company}}",
                body=(
                    "Hi {{first_name}},\n\n"
                    "We found technical SEO issues likely reducing qualified traffic. "
                    "Happy to share prioritized fixes."
                ),
            )
            logger.info("[LeadGenPipeline] Added default step to existing sequence %s", sequence.get("id"))
            _ = step

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

    def _enroll_contact(self, contact_id: str, sequence_id: str) -> bool:
        """Enroll contact into sequence unless already actively enrolled."""
        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

        enrollment = self.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)

        sequences = self.memory.load_data("sequences")
        for sequence in sequences:
            if sequence.get("id") == sequence_id:
                sequence["total_enrolled"] = int(sequence.get("total_enrolled", 0)) + 1
                sequence["active_enrolled"] = int(sequence.get("active_enrolled", 0)) + 1
                break
        self.memory.save_data("sequences", sequences)
        return True

    def _post_klytics_hook(self, pipeline_result: dict[str, Any]) -> None:
        """Send qualified lead payload to configured KLYTICS webhook."""
        settings = self.config.get("klytics", {})
        if not settings.get("enabled"):
            return

        webhook_url = (settings.get("webhook_url") or "").strip()
        if not webhook_url:
            logger.warning("[LeadGenPipeline] klytics.enabled=true but webhook_url is empty")
            return

        threshold = int(settings.get("lead_threshold", 60))
        if int(pipeline_result.get("icp_score") or 0) < threshold:
            return

        payload = {
            "domain": pipeline_result.get("domain"),
            "icp_score": pipeline_result.get("icp_score"),
            "seo_score": pipeline_result.get("seo_score"),
            "contact_email": pipeline_result.get("contact_email"),
            "timestamp": pipeline_result.get("timestamp"),
        }
        try:
            self._dispatch_webhook(webhook_url, payload)
        except Exception as exc:  # noqa: BLE001
            logger.warning("[LeadGenPipeline] Failed posting KLYTICS webhook: %s", exc)

    def _log_lead(self, pipeline_result: dict[str, Any]) -> None:
        """Persist lead generation event in shared memory log."""
        self.memory.append_shared(
            "leads",
            (
                f"**Domain**: {pipeline_result['domain']}  \n"
                f"**SEO Score**: {pipeline_result['seo_score']}  \n"
                f"**ICP Score**: {pipeline_result.get('icp_score')}  \n"
                f"**Contact**: {pipeline_result.get('contact_email')}  \n"
                f"**Qualified**: {pipeline_result['qualified']}  \n"
                f"**Enrolled**: {pipeline_result['enrolled']}"
            ),
        )

    def _extract_domain(self, url: str) -> str:
        """Extract normalized domain from an input URL."""
        value = url if url.startswith(("http://", "https://")) else f"https://{url}"
        return urlparse(value).netloc.lower()

    def _infer_contact_title(self, email: str | None) -> str:
        """Infer a likely job title from an email local part."""
        local = (email or "").split("@")[0].lower()
        if any(token in local for token in ["ceo", "founder", "owner"]):
            return "Founder"
        if any(token in local for token in ["cto", "tech"]):
            return "CTO"
        if any(token in local for token in ["marketing", "growth", "seo"]):
            return "Head of Growth"
        return "Director"

    def _dispatch_webhook(self, webhook_url: str, payload: dict[str, Any]) -> None:
        """Dispatch webhook payload using requests or urllib fallback."""
        if requests is not None:
            response = requests.post(webhook_url, json=payload, timeout=10)
            response.raise_for_status()
            return

        request = urllib_request.Request(
            webhook_url,
            data=json.dumps(payload).encode("utf-8"),
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        try:
            with urllib_request.urlopen(request, timeout=10) as response:
                status = int(getattr(response, "status", 200))
                if status >= 400:
                    raise RuntimeError(f"Webhook returned HTTP {status}")
        except urllib_error.URLError as exc:
            raise RuntimeError(f"Webhook delivery failed: {exc}") from exc
