from __future__ import annotations

import json
import re
import hashlib
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

from sqlmodel import Session, select

from app.models.core import (
    ActivityEvent,
    AnalyticsPostSnapshot,
    ContentScript,
    ExternalPost,
    Provider,
    Theme,
    VideoAsset,
    VideoStatus,
    WebsiteCompanion,
)

WEBSITE_ROOT = Path("/home/agent/projects/TrueTraceShorts_WebSite")
AUDITS_ROOT = Path("/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/data/youtube_upload_audits")
PACKAGES_ROOT = Path("/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/data/post_candidates")
PUBLIC_URL = "https://truetraceshorts.pages.dev"
YOUTUBE_RE = re.compile(r"(?:shorts/|watch\?v=|youtu\.be/|youtube_video_id['\" ]*[:=]['\" ]*)([A-Za-z0-9_-]{8,})")


@dataclass
class MappingSource:
    id: str
    candidate_id: str | None = None
    package_id: str | None = None
    slug: str | None = None
    title: str | None = None
    short_title: str | None = None
    hook: str | None = None
    youtube_id: str | None = None
    youtube_url: str | None = None
    website_slug: str | None = None
    website_content_path: str | None = None
    family: str | None = None
    script_text: str | None = None
    video_path: str | None = None
    sources: set[str] = field(default_factory=set)

    def merge(self, other: "MappingSource") -> "MappingSource":
        for name in [
            "candidate_id", "package_id", "slug", "title", "short_title", "hook", "youtube_id", "youtube_url",
            "website_slug", "website_content_path", "family", "script_text", "video_path",
        ]:
            if not getattr(self, name) and getattr(other, name):
                setattr(self, name, getattr(other, name))
        self.sources.update(other.sources)
        return self


def _safe_read_json(path: Path) -> dict[str, Any] | None:
    try:
        data = json.loads(path.read_text(encoding="utf-8", errors="replace"))
        return data if isinstance(data, dict) else None
    except Exception:
        return None


def _youtube_id(value: Any) -> str | None:
    if not value:
        return None
    text = str(value)
    match = YOUTUBE_RE.search(text)
    if match:
        return match.group(1)
    if re.fullmatch(r"[A-Za-z0-9_-]{8,}", text):
        return text
    return None


def _frontmatter(path: Path) -> dict[str, str]:
    text = path.read_text(encoding="utf-8", errors="replace")
    if not text.startswith("---"):
        return {}
    end = text.find("\n---", 3)
    if end < 0:
        return {}
    raw = text[3:end]
    data: dict[str, str] = {}
    for line in raw.splitlines():
        if not line.strip() or line.startswith(" ") or ":" not in line:
            continue
        key, value = line.split(":", 1)
        value = value.strip().strip('"').strip("'")
        data[key.strip()] = value
    return data


def _norm(text: str | None) -> str:
    if not text:
        return ""
    text = re.sub(r"#[\w-]+", "", text.lower())
    text = re.sub(r"[^a-z0-9]+", " ", text)
    return " ".join(text.split())


def _tokens(text: str | None) -> set[str]:
    stop = {"the", "this", "that", "with", "your", "you", "don", "dont", "and", "for", "not", "yet", "first", "one"}
    return {t for t in _norm(text).split() if len(t) > 2 and t not in stop}


def _add(records: dict[str, MappingSource], record: MappingSource) -> None:
    key = record.youtube_id or record.candidate_id or record.slug or record.id
    if key in records:
        records[key].merge(record)
    else:
        records[key] = record


def _family_from(value: str | None) -> str:
    if not value:
        return "Everyday Red Flags"
    if value.upper() == "EVERYDAY_RED_FLAG" or value.lower().startswith("everyday"):
        return "Everyday Red Flags"
    return value


def build_source_index() -> list[MappingSource]:
    records: dict[str, MappingSource] = {}
    cand_dir = WEBSITE_ROOT / "data" / "candidates"
    for path in sorted(cand_dir.glob("*.json")) if cand_dir.exists() else []:
        data = _safe_read_json(path) or {}
        yt = _youtube_id(data.get("videoUrl"))
        rec = MappingSource(
            id=data.get("id") or path.stem,
            candidate_id=data.get("id") or path.stem,
            package_id=data.get("id") if str(data.get("id") or "").startswith("package_") else None,
            slug=data.get("slug"),
            title=data.get("title"),
            short_title=data.get("shortTitle"),
            hook=data.get("hook"),
            youtube_id=yt,
            youtube_url=data.get("videoUrl"),
            website_slug=data.get("slug"),
            family=_family_from(data.get("category")),
            sources={"website_candidate"},
        )
        _add(records, rec)

    md_dir = WEBSITE_ROOT / "src" / "content" / "redflags"
    for path in sorted(md_dir.glob("*.md")) if md_dir.exists() else []:
        fm = _frontmatter(path)
        yt = _youtube_id(fm.get("videoUrl"))
        rec = MappingSource(
            id=fm.get("id") or path.stem,
            candidate_id=fm.get("id") or path.stem,
            package_id=fm.get("id") if str(fm.get("id") or "").startswith("package_") else None,
            slug=fm.get("slug") or path.stem,
            title=fm.get("title"),
            short_title=fm.get("shortTitle"),
            hook=fm.get("hook"),
            youtube_id=yt,
            youtube_url=fm.get("videoUrl"),
            website_slug=fm.get("slug") or path.stem,
            website_content_path=str(path),
            family=_family_from(fm.get("category")),
            sources={"website_markdown"},
        )
        _add(records, rec)

    for path in sorted(AUDITS_ROOT.glob("*.json")) if AUDITS_ROOT.exists() else []:
        data = _safe_read_json(path) or {}
        yt = _youtube_id(data.get("youtube_video_id") or data.get("shorts_url") or data.get("watch_url"))
        rec = MappingSource(
            id=data.get("candidate_id") or path.stem,
            candidate_id=data.get("candidate_id"),
            title=data.get("title"),
            youtube_id=yt,
            youtube_url=data.get("shorts_url") or data.get("watch_url") or (f"https://www.youtube.com/shorts/{yt}" if yt else None),
            family="Everyday Red Flags" if str(data.get("candidate_id") or "").startswith("erf-") else None,
            sources={"youtube_upload_audit"},
        )
        _add(records, rec)

    for path in sorted(PACKAGES_ROOT.glob("**/review_package*.json")) if PACKAGES_ROOT.exists() else []:
        data = _safe_read_json(path) or {}
        cid = data.get("candidate_id")
        meta = data.get("video_meta") if isinstance(data.get("video_meta"), dict) else {}
        title = data.get("title") or (meta.get("title") if meta else None)
        output = data.get("output") or data.get("media_cache_path")
        rec = MappingSource(
            id=cid or path.parent.name,
            candidate_id=cid,
            title=title,
            hook=(data.get("script") or "")[:180] if isinstance(data.get("script"), str) else None,
            script_text=data.get("script") if isinstance(data.get("script"), str) else None,
            video_path=str(output) if output else None,
            family="Everyday Red Flags" if str(cid or "").startswith("erf-") else None,
            sources={"review_package"},
        )
        _add(records, rec)

    return list(records.values())


def _candidate_payload(record: MappingSource, score: int, confidence: str, reason: str) -> dict[str, Any]:
    return {
        "type": "content_script",
        "id": record.candidate_id or record.id,
        "candidate_id": record.candidate_id,
        "package_id": record.package_id,
        "title": record.short_title or record.title or record.candidate_id,
        "youtube_title": record.title,
        "family": record.family,
        "content_family": record.family,
        "website_slug": record.website_slug,
        "website_status": "guide exists" if record.website_slug and record.website_content_path else ("guide pending" if record.website_slug else "no guide"),
        "website_url": f"{PUBLIC_URL}/redflags/{record.website_slug}/" if record.website_slug else None,
        "website_content_path": record.website_content_path,
        "youtube_id": record.youtube_id,
        "youtube_url": record.youtube_url,
        "script_text": record.script_text,
        "video_path": record.video_path,
        "score": score,
        "confidence": confidence,
        "confidence_label": {
            "high": "High — matched by YouTube video ID",
            "medium": "Medium — matched by exact title",
            "low": "Low — similar title only",
        }.get(confidence, "Needs review — no strong match"),
        "reason": reason,
        "match_reasons": [reason],
        "sources": sorted(record.sources),
    }


def mapping_candidates_for(title: str | None, youtube_id: str | None, limit: int = 5) -> list[dict[str, Any]]:
    candidates = []
    title_norm = _norm(title)
    title_tokens = _tokens(title)
    for rec in build_source_index():
        if youtube_id and rec.youtube_id == youtube_id:
            source = "website candidate" if "website_candidate" in rec.sources else "markdown redflag" if "website_markdown" in rec.sources else "YouTube upload audit"
            candidates.append(_candidate_payload(rec, 100, "high", f"Matched by YouTube video ID from {source}"))
            continue
        names = [rec.title, rec.short_title, rec.hook, rec.candidate_id, rec.slug]
        if title_norm and any(_norm(n) == title_norm for n in names if n):
            candidates.append(_candidate_payload(rec, 84, "medium", "Matched by exact title"))
            continue
        overlap = max((len(title_tokens & _tokens(n)) for n in names if n), default=0)
        if overlap >= 3:
            score = min(65, overlap * 12)
            candidates.append(_candidate_payload(rec, score, "low", f"Similar title/keyword overlap: {overlap} shared terms"))
    candidates.sort(key=lambda item: item["score"], reverse=True)
    dedup: dict[str, dict[str, Any]] = {}
    for item in candidates:
        key = item.get("youtube_id") or item.get("candidate_id") or item["id"]
        if key not in dedup:
            dedup[key] = item
    return list(dedup.values())[:limit]


def _slugify(text: str) -> str:
    text = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
    return text or "content"


def _get_or_create_family(session: Session, name: str | None) -> Theme:
    name = name or "Everyday Red Flags"
    family = session.exec(select(Theme).where(Theme.title == name)).first()
    if not family:
        family = Theme(title=name, description="Mapped from real YouTube analytics and website/content sources.", status="active")
        session.add(family)
        session.commit()
        session.refresh(family)
    return family


def materialize_candidate(session: Session, candidate: dict[str, Any]) -> tuple[VideoAsset, ContentScript | None, Theme]:
    family = _get_or_create_family(session, candidate.get("family") or candidate.get("content_family"))
    cid = candidate.get("candidate_id") or candidate.get("id") or candidate.get("youtube_id")
    video = session.exec(select(VideoAsset).where(VideoAsset.candidate_id == cid)).first()
    if not video:
        checksum = hashlib.sha256(f"analytics-map:{cid}".encode()).hexdigest()
        video = VideoAsset(
            candidate_id=cid,
            package_id=candidate.get("package_id"),
            source="analytics_mapping",
            file_path=candidate.get("video_path") or "",
            file_size=0,
            checksum=checksum,
            external_youtube_id=candidate.get("youtube_id"),
            working_title=candidate.get("title") or candidate.get("youtube_title") or cid,
            topic_id=family.id,
            script=candidate.get("script_text"),
            status=VideoStatus.imported,
            is_demo=False,
            source_label="Youtube 260606",
        )
    else:
        video.external_youtube_id = video.external_youtube_id or candidate.get("youtube_id")
        video.topic_id = video.topic_id or family.id
        video.package_id = video.package_id or candidate.get("package_id")
        video.working_title = video.working_title or candidate.get("title") or candidate.get("youtube_title")
    session.add(video)
    session.commit()
    session.refresh(video)

    script = session.exec(select(ContentScript).where(ContentScript.package_id == cid)).first()
    if not script:
        script = ContentScript(
            family_id=family.id,
            video_asset_id=video.id,
            package_id=cid,
            title=candidate.get("title") or candidate.get("youtube_title") or cid,
            hook=candidate.get("reason"),
            script_text=candidate.get("script_text"),
            youtube_title=candidate.get("youtube_title"),
            status="measured",
            source_path=candidate.get("website_content_path"),
            performance_summary_json={"website_slug": candidate.get("website_slug"), "website_status": candidate.get("website_status")},
        )
    else:
        script.family_id = script.family_id or family.id
        script.video_asset_id = script.video_asset_id or video.id
        script.performance_summary_json = {**(script.performance_summary_json or {}), "website_slug": candidate.get("website_slug"), "website_status": candidate.get("website_status")}
    session.add(script)

    if candidate.get("website_slug"):
        companion = session.exec(select(WebsiteCompanion).where(WebsiteCompanion.video_asset_id == video.id)).first()
        if not companion:
            companion = WebsiteCompanion(
                video_asset_id=video.id,
                content_script_id=script.id,
                website_repo_path=str(WEBSITE_ROOT),
                slug=candidate.get("website_slug"),
                title=candidate.get("title") or candidate.get("youtube_title"),
                generated_content_path=candidate.get("website_content_path"),
                status="pushed" if candidate.get("website_content_path") else "generated",
            )
            session.add(companion)
    session.commit()
    session.refresh(video)
    return video, script, family


def high_confidence_preview(session: Session) -> dict[str, Any]:
    items = []
    snapshots = session.exec(select(AnalyticsPostSnapshot).where(AnalyticsPostSnapshot.is_demo == False)).all()  # noqa: E712
    latest_by_id: dict[str, AnalyticsPostSnapshot] = {}
    for snap in snapshots:
        current = latest_by_id.get(snap.external_post_id)
        if not current or snap.snapshot_at > current.snapshot_at:
            latest_by_id[snap.external_post_id] = snap
    for snap in latest_by_id.values():
        post = session.exec(select(ExternalPost).where(ExternalPost.external_post_id == snap.external_post_id)).first()
        if snap.video_asset_id or (post and (post.video_asset_id or post.mapping_status == "ignored")):
            continue
        candidates = mapping_candidates_for(snap.title, snap.external_post_id, limit=5)
        high = [c for c in candidates if c["confidence"] == "high"]
        if len(high) == 1:
            items.append({"external_post_id": snap.external_post_id, "title": snap.title, "views": snap.views or 0, "target": high[0], "reason": high[0]["reason"]})
    return {"items": sorted(items, key=lambda i: i["views"], reverse=True), "count": len(items)}


def auto_link_high_confidence(session: Session) -> dict[str, Any]:
    preview = high_confidence_preview(session)
    linked = []
    for item in preview["items"]:
        target = item["target"]
        video, script, family = materialize_candidate(session, target)
        post = session.exec(select(ExternalPost).where(ExternalPost.external_post_id == item["external_post_id"])).first()
        if not post:
            continue
        post.video_asset_id = video.id
        post.topic_id = family.id
        post.mapping_status = "linked"
        session.add(post)
        for snap in session.exec(select(AnalyticsPostSnapshot).where(AnalyticsPostSnapshot.external_post_id == item["external_post_id"])).all():
            snap.video_asset_id = video.id
            session.add(snap)
        session.add(ActivityEvent(
            entity_type="analytics_post",
            entity_id=post.id,
            event_type="analytics_auto_linked",
            label="Analytics auto-linked by high-confidence match",
            payload_json={"external_post_id": item["external_post_id"], "video_asset_id": video.id, "content_script_id": script.id if script else None, "family_id": family.id, "reason": item["reason"], "target": target},
        ))
        linked.append({**item, "video_asset_id": video.id, "family_id": family.id, "content_script_id": script.id if script else None})
    session.commit()
    return {"ok": True, "linked_count": len(linked), "linked": linked}
