"""DAO helpers for external trend example discovery and analysis."""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from datetime import UTC, datetime
from typing import Any, Optional

from common.database import DBConfig, Db
from common.logging import get_logger

logger = get_logger(__name__)


@dataclass(frozen=True)
class ExternalTrendAnalysisPayload:
    """Distilled analysis payload for a public external video example."""

    summary_text: str
    hook_pattern: str
    pacing_pattern: str
    payoff_pattern: str
    clipping_lessons: list[str] = field(default_factory=list)


@dataclass(frozen=True)
class ExternalTrendExampleRecord:
    """Stored external trend example with discovery and optional analysis data."""

    id: int
    platform: str
    external_id: str
    source_url: str
    category_seed: str
    matched_query: str
    title: str
    channel_id: str
    channel_name: str
    published_at: datetime
    duration_seconds: int
    view_count: int
    like_count: int
    comment_count: int
    used_chart_fallback: bool
    discovered_at: datetime
    last_seen_at: datetime
    streamer_verified: bool
    streamer_verified_at: Optional[datetime]
    verification_reason: Optional[str]
    brand_excluded: bool
    analysis_status: Optional[str]
    analysis_prompt_version: Optional[str]
    analyzed_at: Optional[datetime]
    summary_text: Optional[str]
    analysis_payload: Optional[ExternalTrendAnalysisPayload]
    last_error: Optional[str]
    next_retry_at: Optional[datetime]


EXTERNAL_TREND_SELECT_COLUMNS = """
    id,
    platform,
    external_id,
    source_url,
    category_seed,
    matched_query,
    title,
    channel_id,
    channel_name,
    published_at,
    duration_seconds,
    view_count,
    like_count,
    comment_count,
    used_chart_fallback,
    discovered_at,
    last_seen_at,
    streamer_verified,
    streamer_verified_at,
    verification_reason,
    brand_excluded,
    analysis_status,
    analysis_prompt_version,
    analyzed_at,
    summary_text,
    analysis_payload,
    last_error,
    next_retry_at
"""


class ExternalTrendDAO:
    """Persistence layer for streamer-qualified external trend examples."""

    def __init__(self, db: Optional[Db] = None):
        self.db = db if db else Db(DBConfig())

    async def upsert_verified_example(
        self,
        *,
        platform: str,
        external_id: str,
        source_url: str,
        category_seed: str,
        matched_query: str,
        title: str,
        channel_id: str,
        channel_name: str,
        published_at: datetime,
        duration_seconds: int,
        view_count: int,
        like_count: int,
        comment_count: int,
        used_chart_fallback: bool,
        verification_reason: str,
        brand_excluded: bool = False,
    ) -> Optional[ExternalTrendExampleRecord]:
        """Create or update a verified example while preserving existing analysis."""
        query = f"""
            INSERT INTO external_trend_examples (
                platform,
                external_id,
                source_url,
                category_seed,
                matched_query,
                title,
                channel_id,
                channel_name,
                published_at,
                duration_seconds,
                view_count,
                like_count,
                comment_count,
                used_chart_fallback,
                discovered_at,
                last_seen_at,
                streamer_verified,
                streamer_verified_at,
                verification_reason,
                brand_excluded
            )
            VALUES (
                %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
                TRUE, %s, %s, %s
            )
            ON CONFLICT (platform, external_id, category_seed) DO UPDATE
            SET source_url = EXCLUDED.source_url,
                matched_query = EXCLUDED.matched_query,
                title = EXCLUDED.title,
                channel_id = EXCLUDED.channel_id,
                channel_name = EXCLUDED.channel_name,
                published_at = EXCLUDED.published_at,
                duration_seconds = EXCLUDED.duration_seconds,
                view_count = EXCLUDED.view_count,
                like_count = EXCLUDED.like_count,
                comment_count = EXCLUDED.comment_count,
                used_chart_fallback = EXCLUDED.used_chart_fallback,
                last_seen_at = EXCLUDED.last_seen_at,
                streamer_verified = EXCLUDED.streamer_verified,
                streamer_verified_at = EXCLUDED.streamer_verified_at,
                verification_reason = EXCLUDED.verification_reason,
                brand_excluded = EXCLUDED.brand_excluded
            RETURNING {EXTERNAL_TREND_SELECT_COLUMNS}
        """
        now = datetime.now(UTC)
        row = await self.db.fetch_one(
            query,
            (
                platform,
                external_id,
                source_url,
                category_seed,
                matched_query,
                title,
                channel_id,
                channel_name,
                published_at,
                duration_seconds,
                view_count,
                like_count,
                comment_count,
                used_chart_fallback,
                now,
                now,
                now,
                verification_reason,
                brand_excluded,
            ),
        )
        return self._row_to_record(row) if row else None

    async def get_analyzed_examples_for_category(
        self,
        *,
        category_seed: str,
        analysis_prompt_version: str,
        freshness_days: int,
        limit: int,
    ) -> list[ExternalTrendExampleRecord]:
        """Return fresh analyzed streamer-qualified examples for prompt building."""
        query = f"""
            SELECT {EXTERNAL_TREND_SELECT_COLUMNS}
            FROM external_trend_examples
            WHERE LOWER(category_seed) = LOWER(%s)
              AND streamer_verified IS TRUE
              AND COALESCE(brand_excluded, FALSE) IS FALSE
              AND analysis_status = 'completed'
              AND analysis_prompt_version = %s
              AND analyzed_at IS NOT NULL
              AND analyzed_at >= NOW() - (%s * INTERVAL '1 day')
            ORDER BY view_count DESC, like_count DESC, comment_count DESC, published_at DESC
            LIMIT %s
        """
        rows = await self.db.fetch_all(
            query,
            (
                category_seed,
                analysis_prompt_version,
                max(1, freshness_days),
                max(1, limit),
            ),
        )
        return [self._row_to_record(row) for row in rows or []]

    async def get_verified_examples_for_category(
        self,
        *,
        category_seed: str,
        limit: int,
    ) -> list[ExternalTrendExampleRecord]:
        """Return verified streamer examples for metadata prompt fallback."""
        query = f"""
            SELECT {EXTERNAL_TREND_SELECT_COLUMNS}
            FROM external_trend_examples
            WHERE LOWER(category_seed) = LOWER(%s)
              AND streamer_verified IS TRUE
              AND COALESCE(brand_excluded, FALSE) IS FALSE
            ORDER BY view_count DESC, like_count DESC, comment_count DESC, published_at DESC
            LIMIT %s
        """
        rows = await self.db.fetch_all(query, (category_seed, max(1, limit)))
        return [self._row_to_record(row) for row in rows or []]

    async def list_recent_category_seeds(
        self,
        *,
        lookback_days: int,
        limit: int,
    ) -> list[str]:
        """Return recent active category seeds from TL;DR stream activity."""
        query = """
            WITH recent_categories AS (
                SELECT
                    TRIM(game_name) AS category_seed,
                    LOWER(TRIM(game_name)) AS normalized_seed,
                    COALESCE(stream_end_time, stream_start_time) AS activity_at
                FROM streams
                WHERE game_name IS NOT NULL
                  AND TRIM(game_name) <> ''
                  AND LOWER(TRIM(game_name)) NOT IN ('unknown', 'none', 'null')
                  AND stream_start_time >= NOW() - (%s * INTERVAL '1 day')
            )
            SELECT (ARRAY_AGG(category_seed ORDER BY activity_at DESC))[1] AS category_seed
            FROM recent_categories
            GROUP BY normalized_seed
            ORDER BY COUNT(*) DESC, MAX(activity_at) DESC
            LIMIT %s
        """
        rows = await self.db.fetch_all(
            query,
            (
                max(1, lookback_days),
                max(1, limit),
            ),
        )
        return [str(row[0]) for row in rows or [] if row and row[0]]

    async def claim_next_example_for_analysis(
        self,
        *,
        analysis_prompt_version: str,
        freshness_days: int,
    ) -> Optional[ExternalTrendExampleRecord]:
        """Claim one eligible verified example for background Gemini analysis."""
        query = f"""
            WITH candidate AS (
                SELECT id
                FROM external_trend_examples
                WHERE streamer_verified IS TRUE
                  AND COALESCE(brand_excluded, FALSE) IS FALSE
                  AND source_url IS NOT NULL
                  AND (
                        analysis_status IS NULL
                     OR analysis_status = 'pending'
                     OR analyzed_at IS NULL
                     OR analysis_prompt_version IS DISTINCT FROM %s
                     OR analyzed_at < NOW() - (%s * INTERVAL '1 day')
                     OR (
                            analysis_status = 'failed'
                        AND (next_retry_at IS NULL OR next_retry_at <= NOW())
                     )
                  )
                ORDER BY last_seen_at DESC, view_count DESC, published_at DESC
                LIMIT 1
                FOR UPDATE SKIP LOCKED
            )
            UPDATE external_trend_examples AS ete
            SET analysis_status = 'analyzing'
            FROM candidate
            WHERE ete.id = candidate.id
            RETURNING {EXTERNAL_TREND_SELECT_COLUMNS}
        """
        row = await self.db.fetch_one(
            query,
            (
                analysis_prompt_version,
                max(1, freshness_days),
            ),
        )
        return self._row_to_record(row) if row else None

    async def mark_analysis_completed(
        self,
        *,
        record_id: int,
        analysis_prompt_version: str,
        analysis_payload: ExternalTrendAnalysisPayload,
    ) -> Optional[ExternalTrendExampleRecord]:
        """Persist a successful analysis payload."""
        query = f"""
            UPDATE external_trend_examples
            SET analysis_status = 'completed',
                analysis_prompt_version = %s,
                analyzed_at = %s,
                summary_text = %s,
                analysis_payload = %s,
                last_error = NULL,
                next_retry_at = NULL
            WHERE id = %s
            RETURNING {EXTERNAL_TREND_SELECT_COLUMNS}
        """
        analyzed_at = datetime.now(UTC)
        row = await self.db.fetch_one(
            query,
            (
                analysis_prompt_version,
                analyzed_at,
                analysis_payload.summary_text,
                json.dumps(
                    {
                        "summary_text": analysis_payload.summary_text,
                        "hook_pattern": analysis_payload.hook_pattern,
                        "pacing_pattern": analysis_payload.pacing_pattern,
                        "payoff_pattern": analysis_payload.payoff_pattern,
                        "clipping_lessons": list(analysis_payload.clipping_lessons),
                    }
                ),
                record_id,
            ),
        )
        return self._row_to_record(row) if row else None

    async def mark_analysis_failed(
        self,
        *,
        record_id: int,
        error: str,
        retry_backoff_seconds: int,
    ) -> Optional[ExternalTrendExampleRecord]:
        """Persist a failed analysis attempt and retry backoff."""
        query = f"""
            UPDATE external_trend_examples
            SET analysis_status = 'failed',
                last_error = %s,
                next_retry_at = NOW() + (%s * INTERVAL '1 second')
            WHERE id = %s
            RETURNING {EXTERNAL_TREND_SELECT_COLUMNS}
        """
        row = await self.db.fetch_one(
            query,
            (
                error,
                max(1, retry_backoff_seconds),
                record_id,
            ),
        )
        return self._row_to_record(row) if row else None

    async def delete_expired_records(self, *, retention_days: int) -> None:
        """Delete old records that are well outside the active freshness window."""
        query = """
            DELETE FROM external_trend_examples
            WHERE last_seen_at < NOW() - (%s * INTERVAL '1 day')
        """
        await self.db.execute_commit(query, (max(1, retention_days),))

    def _row_to_record(
        self,
        row: tuple[Any, ...] | None,
    ) -> ExternalTrendExampleRecord:
        if row is None:
            raise ValueError("Cannot map empty external trend row")

        analysis_payload = self._parse_analysis_payload(row[25])
        return ExternalTrendExampleRecord(
            id=int(row[0]),
            platform=str(row[1]),
            external_id=str(row[2]),
            source_url=str(row[3]),
            category_seed=str(row[4]),
            matched_query=str(row[5]),
            title=str(row[6]),
            channel_id=str(row[7]),
            channel_name=str(row[8]),
            published_at=row[9],
            duration_seconds=int(row[10] or 0),
            view_count=int(row[11] or 0),
            like_count=int(row[12] or 0),
            comment_count=int(row[13] or 0),
            used_chart_fallback=bool(row[14]),
            discovered_at=row[15],
            last_seen_at=row[16],
            streamer_verified=bool(row[17]),
            streamer_verified_at=row[18],
            verification_reason=row[19],
            brand_excluded=bool(row[20]),
            analysis_status=row[21],
            analysis_prompt_version=row[22],
            analyzed_at=row[23],
            summary_text=row[24],
            analysis_payload=analysis_payload,
            last_error=row[26],
            next_retry_at=row[27],
        )

    def _parse_analysis_payload(
        self, value: Any
    ) -> Optional[ExternalTrendAnalysisPayload]:
        if value is None:
            return None
        if isinstance(value, ExternalTrendAnalysisPayload):
            return value

        payload: dict[str, Any]
        if isinstance(value, str):
            try:
                payload = json.loads(value)
            except json.JSONDecodeError:
                return None
        elif isinstance(value, dict):
            payload = value
        else:
            return None

        return ExternalTrendAnalysisPayload(
            summary_text=str(payload.get("summary_text") or ""),
            hook_pattern=str(payload.get("hook_pattern") or ""),
            pacing_pattern=str(payload.get("pacing_pattern") or ""),
            payoff_pattern=str(payload.get("payoff_pattern") or ""),
            clipping_lessons=[
                str(item).strip()
                for item in payload.get("clipping_lessons", [])
                if str(item).strip()
            ][:3],
        )
