#!/usr/bin/env python3
"""
Generate dashboard data JSON from analysis results and database stats.
Supports channel filtering and includes channel metadata for the UI.

Run: python3 generate_dashboard_data.py [--channel CHANNEL_ID]
"""

import argparse
import json
import sqlite3
from pathlib import Path
from datetime import datetime
from typing import Optional, Dict, List

OUTPUT_DIR = Path(__file__).parent / "output"
DB_PATH = OUTPUT_DIR / "trends_history.db"
DASHBOARD_DATA_PATH = OUTPUT_DIR / "dashboard_data.json"

# Channel IDs (used to identify channel-specific files vs timestamped backups)
CHANNEL_IDS = [
    "how_things_work",
    "brain_is_lying",
    "power_works",
    "money_traps",
    "body_is_weird",
    "what_actually_happened"
]

# Map old opportunity file channel IDs to new dashboard channel IDs
CHANNEL_ID_MAP = {
    "how_things_work": "how_it_actually_works",
    "brain_is_lying": "why_you_do_that",
    "power_works": "designed_to_trick_you",
    "money_traps": "the_money_thing",
    "body_is_weird": "what_happens_next",
    "what_actually_happened": "one_minute_history",
}


def safe_get(obj, *keys, default=""):
    """Safely traverse nested dict/objects."""
    val = obj
    for key in keys:
        if val is None:
            return default
        if isinstance(val, dict):
            val = val.get(key)
        else:
            return default
    if val is None:
        return default
    if isinstance(val, dict):
        # Try common text keys
        for k in ["text", "value", "content", "description", "primary", "main"]:
            if k in val:
                return val[k]
        return str(val)
    return val


def format_content_field(val) -> str:
    """Format a potentially nested content field as readable HTML text."""
    if val is None:
        return ""
    if isinstance(val, str):
        return val
    if isinstance(val, list):
        # Join list items as bullet points
        items = []
        for item in val:
            if isinstance(item, str):
                items.append(f"• {item}")
            elif isinstance(item, dict):
                # Try to extract text from dict
                text = item.get("text") or item.get("content") or item.get("description") or str(item)
                items.append(f"• {text}")
        return "<br>".join(items)
    if isinstance(val, dict):
        # Format dict as labeled sections with HTML
        parts = []
        for key, content in val.items():
            label = key.replace("_", " ").title()
            if isinstance(content, list):
                content_text = "; ".join(str(c)[:100] for c in content[:3])
                if len(content) > 3:
                    content_text += f" (+{len(content) - 3} more)"
            elif isinstance(content, dict):
                content_text = format_content_field(content)
            else:
                content_text = str(content)
            parts.append(f"<strong>{label}:</strong> {content_text}")
        return "<br><br>".join(parts)
    return str(val)


def get_first_sentence(text):
    """Extract first sentence from text."""
    if not text:
        return ""
    text = str(text)
    for end in [". ", "! ", "? "]:
        if end in text:
            return text[:text.index(end) + 1]
    return text[:150] + "..." if len(text) > 150 else text


def get_pipeline_stats() -> dict:
    """Get pipeline statistics from the database."""
    stats = {
        "total_items": 0,
        "last_collection": "Never",
        "last_analysis": "Never",
        "new_items": 0,
        "summary": "No data yet"
    }

    if not DB_PATH.exists():
        return stats

    conn = sqlite3.connect(DB_PATH)
    cursor = conn.cursor()

    # Total items
    cursor.execute("SELECT COUNT(*) FROM trend_items")
    stats["total_items"] = cursor.fetchone()[0]

    # Last collection
    cursor.execute("SELECT run_date FROM collection_runs ORDER BY id DESC LIMIT 1")
    last_run = cursor.fetchone()
    if last_run:
        try:
            dt = datetime.fromisoformat(last_run[0])
            stats["last_collection"] = dt.strftime("%Y-%m-%d %H:%M")
        except:
            stats["last_collection"] = last_run[0][:16] if last_run[0] else "Unknown"

    conn.close()
    return stats


def transform_opportunity(opp: dict, index: int) -> dict:
    """Transform analysis opportunity to dashboard format."""
    # Get sources - check direct field first, then nested
    sources = opp.get("sources", [])
    if isinstance(sources, list) and sources:
        sources = set(sources)
    else:
        sources = set()
        topics = opp.get("topics_in_cluster", opp.get("cluster_topics", []))
        for t in topics:
            if isinstance(t, dict) and t.get("source"):
                sources.add(t["source"])
        for m in opp.get("merged_from", []):
            if isinstance(m, dict) and m.get("source"):
                sources.add(m["source"])

    # Convert 0-5 score to 0-100
    raw_score = opp.get("weighted_score", 0)
    score_100 = int(raw_score * 20) if raw_score <= 5 else int(raw_score)

    # Extract confusion analysis
    confusion = opp.get("confusion_analysis", {})
    if isinstance(confusion, str):
        confusion = {"primary_question": confusion}
    elif not isinstance(confusion, dict):
        confusion = {}
    primary_q = safe_get(confusion, "primary_question") or safe_get(confusion, "primary")
    secondary_qs = confusion.get("secondary_questions", confusion.get("secondary", []))
    if isinstance(secondary_qs, list):
        secondary_qs = [safe_get(q, "question") if isinstance(q, dict) else str(q) for q in secondary_qs]
    else:
        secondary_qs = []

    # Extract audience
    audience = opp.get("target_audience", {})

    # Extract dimensions/scores
    scores = opp.get("scores", {})
    dimensions = {}
    dimension_names = {
        "demand_signal": "Demand",
        "content_gap": "Gap",
        "explainability": "Explainability",
        "evergreen_potential": "Evergreen",
        "audience_breadth": "Breadth",
        "competition_gap": "Competition"
    }
    for key, label in dimension_names.items():
        dim = scores.get(key, {})
        if isinstance(dim, dict):
            dimensions[label] = {
                "score": dim.get("score", 0),
                "reasoning": dim.get("rationale", "")
            }
        elif isinstance(dim, (int, float)):
            dimensions[label] = {"score": dim, "reasoning": ""}

    # Extract structure - prefer new video_structure from Layer 4
    video_structure = opp.get("video_structure", {})
    if video_structure and not video_structure.get("legacy"):
        # New Layer 4 format
        sections = video_structure.get("sections", [])
        structure = [
            {
                "section": s.get("title", f"Section {s.get('section_number', i+1)}"),
                "timestamp": s.get("timestamp", ""),
                "duration_seconds": s.get("duration_seconds", 0),
                "purpose": s.get("purpose", ""),
                "content": s.get("content", ""),
                "visuals": s.get("visuals", ""),
                "hook_to_next": s.get("hook_to_next", "")
            }
            for i, s in enumerate(sections)
        ]
        video_meta = {
            "total_duration": video_structure.get("total_duration", ""),
            "retention_strategy": video_structure.get("retention_strategy", ""),
            "key_retention_moments": video_structure.get("key_retention_moments", []),
            "share_triggers": video_structure.get("share_triggers", [])
        }
    else:
        # Legacy structure format
        raw_structure = opp.get("structure", [])
        if isinstance(raw_structure, list):
            structure = [
                {
                    "section": safe_get(s, "section") or safe_get(s, "title") or f"Part {i+1}",
                    "timestamp": safe_get(s, "duration") or "",
                    "content": safe_get(s, "content") or safe_get(s, "description") or ""
                }
                for i, s in enumerate(raw_structure)
            ]
        else:
            structure = []
        video_meta = None

    return {
        "id": str(index),
        "verdict": opp.get("verdict", "UNKNOWN"),
        "score": score_100,
        "title": opp.get("suggested_title", opp.get("topic", "Untitled")),
        "theme": safe_get(opp, "theme") or "",
        "sources": list(sources) if sources else ["unknown"],
        "topic_count": opp.get("topic_count", 1),
        "channel": opp.get("channel", ""),  # Channel assignment
        "one_line": get_first_sentence(safe_get(opp, "verdict_reasoning")),
        "missing_angle": safe_get(opp, "missing_angle") or safe_get(opp, "angle") or "",
        "verdict_reasoning": safe_get(opp, "verdict_reasoning") or "",
        "confusion": {
            "primary_question": primary_q or "",
            "secondary_questions": secondary_qs[:5] if secondary_qs else []
        },
        "audience": {
            "the_moment": safe_get(audience, "the_moment") or safe_get(audience, "moment") or "",
            "click_trigger": safe_get(audience, "click_trigger") or "",
            "what_they_know": safe_get(audience, "what_they_know") or "",
            "what_is_wrong": safe_get(audience, "what_they_think_is_wrong") or ""
        },
        "existing_content": format_content_field(opp.get("existing_content", "")),
        "why_existing_fails": format_content_field(opp.get("why_existing_fails", "")),
        "hook": safe_get(opp, "opening_hook") or "",
        "csf": safe_get(opp, "critical_success_factor") or "",
        "structure": structure,
        "video_meta": video_meta,
        "dimensions": dimensions
    }


def count_items_since_analysis(analysis_time: str) -> int:
    """Count items collected since the last analysis."""
    if not analysis_time or not DB_PATH.exists():
        return 0

    conn = sqlite3.connect(DB_PATH)
    cursor = conn.cursor()

    try:
        analysis_dt = datetime.fromisoformat(analysis_time.replace("Z", "+00:00"))
        analysis_str = analysis_dt.replace(tzinfo=None).strftime("%Y-%m-%dT%H:%M:%S")
    except:
        analysis_str = analysis_time

    cursor.execute(
        "SELECT COUNT(*) FROM trend_items WHERE collected_at > ?",
        (analysis_str,)
    )
    count = cursor.fetchone()[0]
    conn.close()
    return count


def load_channels_config() -> Dict:
    """Load channel configurations for the dashboard."""
    try:
        from channel_config import get_enabled_channels, get_channel_stats

        channels = {}
        for ch_id, ch in get_enabled_channels().items():
            stats = get_channel_stats(ch_id)
            channels[ch_id] = {
                "id": ch_id,
                "name": ch.name,
                "short_name": ch.short_name,
                "description": ch.description,
                "color": ch.color,
                "sources": ch.get_all_sources(),
                "item_count": stats["item_count"],
                "last_analysis": stats["last_analysis"]
            }
        return channels
    except ImportError:
        return {}


def get_channel_opportunity_files() -> List[Path]:
    """
    Find all channel-specific opportunity files.

    Returns files like:
    - analysis_opportunities_how_things_work.json
    - analysis_opportunities_brain_is_lying.json

    But NOT timestamped backups like:
    - analysis_opportunities_how_things_work_20260212_123456.json
    """
    files = []
    for channel_id in CHANNEL_IDS:
        path = OUTPUT_DIR / f"analysis_opportunities_{channel_id}.json"
        if path.exists():
            files.append(path)

    # Also check for legacy generic file (for backward compatibility)
    generic_path = OUTPUT_DIR / "analysis_opportunities.json"
    if generic_path.exists() and not files:
        # Only use generic file if no channel-specific files exist
        files.append(generic_path)

    return files


def main(channel_filter: Optional[str] = None):
    """Generate dashboard data, merging all channel opportunity files."""
    print("Generating dashboard data...")
    if channel_filter:
        print(f"  Channel filter: {channel_filter}")

    # Get pipeline stats
    stats = get_pipeline_stats()
    print(f"  Database: {stats['total_items']:,} items")

    # Load channel configurations
    channels = load_channels_config()
    if channels:
        print(f"  Channels configured: {len(channels)}")

    # Find all channel opportunity files
    opp_files = get_channel_opportunity_files()
    print(f"  Opportunity files found: {len(opp_files)}")
    for f in opp_files:
        print(f"    - {f.name}")

    # Load and merge opportunities from all channel files
    opportunities = []
    analysis_time = None
    verdict_counts = {"MAKE_NOW": 0, "WORTH_MAKING": 0, "NEEDS_RESEARCH": 0, "SKIP": 0}
    channel_opp_counts = {}

    global_index = 0
    for opp_file in opp_files:
        with open(opp_file, "r") as f:
            data = json.load(f)

        file_channel_id = data.get("channel_id")
        file_analysis_time = data.get("timestamp") or data.get("analysis_timestamp")

        # Track most recent analysis time
        if file_analysis_time:
            if analysis_time is None or file_analysis_time > analysis_time:
                analysis_time = file_analysis_time

        raw_opps = data.get("opportunities", [])

        # Apply channel filter if specified
        if channel_filter and file_channel_id != channel_filter:
            continue

        # Map old channel ID to new dashboard channel ID
        mapped_channel_id = CHANNEL_ID_MAP.get(file_channel_id, file_channel_id)

        # Track count per channel (using mapped ID)
        if mapped_channel_id:
            channel_opp_counts[mapped_channel_id] = len(raw_opps)

        for opp in raw_opps:
            # Always set channel to the mapped ID (overwrite old IDs)
            opp["channel"] = mapped_channel_id

            transformed = transform_opportunity(opp, global_index)
            # Also remap if transform read an old channel ID
            old_channel = transformed.get("channel", "")
            transformed["channel"] = CHANNEL_ID_MAP.get(old_channel, old_channel) or mapped_channel_id

            opportunities.append(transformed)
            global_index += 1

            v = transformed["verdict"]
            if v in verdict_counts:
                verdict_counts[v] += 1

    # Sort all opportunities by score
    opportunities.sort(key=lambda x: x.get("score", 0), reverse=True)

    # Re-assign IDs after sorting
    for i, opp in enumerate(opportunities):
        opp["id"] = str(i)

    print(f"  Total opportunities: {len(opportunities)}")
    for ch_id, count in channel_opp_counts.items():
        print(f"    - {ch_id}: {count}")

    # Update stats
    if analysis_time:
        try:
            dt = datetime.fromisoformat(analysis_time.replace("Z", "+00:00"))
            stats["last_analysis"] = dt.strftime("%Y-%m-%d %H:%M")
        except:
            stats["last_analysis"] = analysis_time[:16] if analysis_time else "Unknown"

    stats["new_items"] = count_items_since_analysis(analysis_time) if analysis_time else 0

    # Build summary
    parts = [f"{len(opportunities)} opportunities"]
    for v in ["MAKE_NOW", "WORTH_MAKING", "NEEDS_RESEARCH", "SKIP"]:
        if verdict_counts[v] > 0:
            parts.append(f"{verdict_counts[v]} {v.replace('_', ' ')}")
    stats["summary"] = ", ".join(parts)

    print(f"  Summary: {stats['summary']}")

    # Get current channel info if filtered
    current_channel = None
    if channel_filter and channel_filter in channels:
        current_channel = channels[channel_filter]

    # Write output
    dashboard_data = {
        "stats": stats,
        "opportunities": opportunities,
        "channels": channels,
        "current_channel": current_channel
    }

    with open(DASHBOARD_DATA_PATH, "w") as f:
        json.dump(dashboard_data, f, indent=2)

    print(f"\nDashboard data written to: {DASHBOARD_DATA_PATH}")
    print(f"Open: file://{OUTPUT_DIR}/dashboard.html")
    print(f"  Or: python3 -m http.server 8000 --directory {OUTPUT_DIR}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Generate dashboard data")
    parser.add_argument(
        "--channel",
        type=str,
        help="Filter to a specific channel"
    )
    args = parser.parse_args()

    main(channel_filter=args.channel)
