"""
Project data service for Whisper Scribe 3.0 - Knowledge Repository.

Each video becomes a "project" with extracted entities, insights, quotes,
semantic sections, personal notes, and cross-video connections.
"""

import json
import os
import uuid
from datetime import datetime
from pathlib import Path
from typing import List, Optional, Dict, Any
from dataclasses import dataclass, field, asdict
from enum import Enum
from ..config import get_config_dir


# =============================================================================
# Data Models (Internal)
# =============================================================================

class EntityType(str, Enum):
    """Types of entities that can be extracted."""
    PERSON = "person"
    ORGANIZATION = "organization"
    TECHNOLOGY = "technology"
    PRODUCT = "product"
    PLACE = "place"
    CONCEPT = "concept"


class InsightCategory(str, Enum):
    """Categories for extracted insights."""
    TAKEAWAY = "takeaway"      # Key point to remember
    AHA = "aha"                # Aha moment / revelation
    WISDOM = "wisdom"          # General wisdom / principle
    ACTION = "action"          # Action item / to-do
    WARNING = "warning"        # Caution / warning
    DEFINITION = "definition"  # Term or concept definition


class FactCategory(str, Enum):
    """Categories for extracted facts."""
    STATISTIC = "statistic"    # Numbers, percentages, metrics
    DEFINITION = "definition"  # Definition of a term
    CLAIM = "claim"            # Stated claim or assertion
    DATE = "date"              # Historical date or event
    COMPARISON = "comparison"  # Comparison between things


@dataclass
class Mention:
    """A mention of an entity at a specific timestamp."""
    timestamp: float  # Seconds into video
    context: str      # Surrounding text for context


@dataclass
class Entity:
    """An extracted entity (person, org, technology, etc.)."""
    id: str
    name: str
    type: EntityType
    description: str = ""
    mentions: List[Mention] = field(default_factory=list)
    wikipedia_url: Optional[str] = None
    confidence: float = 1.0

    def to_dict(self) -> dict:
        return {
            'id': self.id,
            'name': self.name,
            'type': self.type.value if isinstance(self.type, EntityType) else self.type,
            'description': self.description,
            'mentions': [{'timestamp': m.timestamp, 'context': m.context} for m in self.mentions],
            'wikipedia_url': self.wikipedia_url,
            'confidence': self.confidence
        }

    @classmethod
    def from_dict(cls, data: dict) -> 'Entity':
        mentions = [Mention(**m) for m in data.get('mentions', [])]
        entity_type = data.get('type', 'concept')
        if isinstance(entity_type, str):
            try:
                entity_type = EntityType(entity_type)
            except ValueError:
                entity_type = EntityType.CONCEPT
        return cls(
            id=data.get('id', str(uuid.uuid4())),
            name=data['name'],
            type=entity_type,
            description=data.get('description', ''),
            mentions=mentions,
            wikipedia_url=data.get('wikipedia_url'),
            confidence=data.get('confidence', 1.0)
        )


@dataclass
class ExtractedEntities:
    """Collection of all extracted entities by type."""
    people: List[Entity] = field(default_factory=list)
    organizations: List[Entity] = field(default_factory=list)
    technologies: List[Entity] = field(default_factory=list)
    products: List[Entity] = field(default_factory=list)
    places: List[Entity] = field(default_factory=list)
    concepts: List[Entity] = field(default_factory=list)

    def to_dict(self) -> dict:
        return {
            'people': [e.to_dict() for e in self.people],
            'organizations': [e.to_dict() for e in self.organizations],
            'technologies': [e.to_dict() for e in self.technologies],
            'products': [e.to_dict() for e in self.products],
            'places': [e.to_dict() for e in self.places],
            'concepts': [e.to_dict() for e in self.concepts]
        }

    @classmethod
    def from_dict(cls, data: dict) -> 'ExtractedEntities':
        return cls(
            people=[Entity.from_dict(e) for e in data.get('people', [])],
            organizations=[Entity.from_dict(e) for e in data.get('organizations', [])],
            technologies=[Entity.from_dict(e) for e in data.get('technologies', [])],
            products=[Entity.from_dict(e) for e in data.get('products', [])],
            places=[Entity.from_dict(e) for e in data.get('places', [])],
            concepts=[Entity.from_dict(e) for e in data.get('concepts', [])]
        )

    def all_entities(self) -> List[Entity]:
        """Get all entities as a flat list."""
        return (
            self.people + self.organizations + self.technologies +
            self.products + self.places + self.concepts
        )

    def count(self) -> int:
        """Total number of entities."""
        return len(self.all_entities())


@dataclass
class ExtractedInsight:
    """An extracted insight or key takeaway."""
    id: str
    content: str
    category: InsightCategory
    confidence: float = 1.0
    source_timestamp: Optional[float] = None  # Where in video this came from

    def to_dict(self) -> dict:
        return {
            'id': self.id,
            'content': self.content,
            'category': self.category.value if isinstance(self.category, InsightCategory) else self.category,
            'confidence': self.confidence,
            'source_timestamp': self.source_timestamp
        }

    @classmethod
    def from_dict(cls, data: dict) -> 'ExtractedInsight':
        category = data.get('category', 'takeaway')
        if isinstance(category, str):
            try:
                category = InsightCategory(category)
            except ValueError:
                category = InsightCategory.TAKEAWAY
        return cls(
            id=data.get('id', str(uuid.uuid4())),
            content=data['content'],
            category=category,
            confidence=data.get('confidence', 1.0),
            source_timestamp=data.get('source_timestamp')
        )


@dataclass
class ExtractedQuote:
    """A notable quote from the video."""
    id: str
    text: str
    speaker: Optional[str] = None  # Who said it (if known)
    timestamp: float = 0.0         # When in the video
    context: str = ""              # Surrounding context

    def to_dict(self) -> dict:
        return {
            'id': self.id,
            'text': self.text,
            'speaker': self.speaker,
            'timestamp': self.timestamp,
            'context': self.context
        }

    @classmethod
    def from_dict(cls, data: dict) -> 'ExtractedQuote':
        return cls(
            id=data.get('id', str(uuid.uuid4())),
            text=data['text'],
            speaker=data.get('speaker'),
            timestamp=data.get('timestamp', 0.0),
            context=data.get('context', '')
        )


@dataclass
class ExtractedFact:
    """An extracted fact, statistic, or claim."""
    id: str
    statement: str
    category: FactCategory
    confidence: float = 1.0
    source_timestamp: float = 0.0
    source: Optional[str] = None  # Attribution if any

    def to_dict(self) -> dict:
        return {
            'id': self.id,
            'statement': self.statement,
            'category': self.category.value if isinstance(self.category, FactCategory) else self.category,
            'confidence': self.confidence,
            'source_timestamp': self.source_timestamp,
            'source': self.source
        }

    @classmethod
    def from_dict(cls, data: dict) -> 'ExtractedFact':
        category = data.get('category', 'claim')
        if isinstance(category, str):
            try:
                category = FactCategory(category)
            except ValueError:
                category = FactCategory.CLAIM
        return cls(
            id=data.get('id', str(uuid.uuid4())),
            statement=data['statement'],
            category=category,
            confidence=data.get('confidence', 1.0),
            source_timestamp=data.get('source_timestamp', 0.0),
            source=data.get('source')
        )


@dataclass
class SemanticSection:
    """A semantic section/chunk of the transcription."""
    id: str
    title: str              # AI-generated section title
    content: str            # The text content
    start_time: float       # Start timestamp
    end_time: float         # End timestamp
    topics: List[str] = field(default_factory=list)  # Topics covered
    summary: str = ""       # Brief summary

    def to_dict(self) -> dict:
        return {
            'id': self.id,
            'title': self.title,
            'content': self.content,
            'start_time': self.start_time,
            'end_time': self.end_time,
            'topics': self.topics,
            'summary': self.summary
        }

    @classmethod
    def from_dict(cls, data: dict) -> 'SemanticSection':
        return cls(
            id=data.get('id', str(uuid.uuid4())),
            title=data['title'],
            content=data['content'],
            start_time=data.get('start_time', 0.0),
            end_time=data.get('end_time', 0.0),
            topics=data.get('topics', []),
            summary=data.get('summary', '')
        )


@dataclass
class PersonalNote:
    """A personal note added by the user."""
    id: str
    content: str
    timestamp: Optional[float] = None  # Linked to video position
    created_at: str = ""
    updated_at: str = ""
    tags: List[str] = field(default_factory=list)

    def __post_init__(self):
        if not self.created_at:
            self.created_at = datetime.now().isoformat()
        if not self.updated_at:
            self.updated_at = self.created_at

    def to_dict(self) -> dict:
        return {
            'id': self.id,
            'content': self.content,
            'timestamp': self.timestamp,
            'created_at': self.created_at,
            'updated_at': self.updated_at,
            'tags': self.tags
        }

    @classmethod
    def from_dict(cls, data: dict) -> 'PersonalNote':
        return cls(
            id=data.get('id', str(uuid.uuid4())),
            content=data['content'],
            timestamp=data.get('timestamp'),
            created_at=data.get('created_at', ''),
            updated_at=data.get('updated_at', ''),
            tags=data.get('tags', [])
        )


@dataclass
class VideoConnection:
    """Connection to another video."""
    video_id: str
    title: str
    similarity_score: float = 0.0
    shared_topics: List[str] = field(default_factory=list)
    shared_entities: List[str] = field(default_factory=list)

    def to_dict(self) -> dict:
        return {
            'video_id': self.video_id,
            'title': self.title,
            'similarity_score': self.similarity_score,
            'shared_topics': self.shared_topics,
            'shared_entities': self.shared_entities
        }

    @classmethod
    def from_dict(cls, data: dict) -> 'VideoConnection':
        return cls(
            video_id=data['video_id'],
            title=data.get('title', ''),
            similarity_score=data.get('similarity_score', 0.0),
            shared_topics=data.get('shared_topics', []),
            shared_entities=data.get('shared_entities', [])
        )


@dataclass
class ProjectData:
    """
    Complete project data for a video.
    This is the v3 format that extends the v2 history item.
    """
    # Core identification
    video_id: str
    title: str
    url: str
    channel: str = ""
    duration: int = 0  # Duration in seconds
    thumbnail: str = ""
    file_path: str = ""

    # Transcription
    transcription: str = ""
    language: str = ""
    text_direction: str = "ltr"

    # Semantic sections
    sections: List[SemanticSection] = field(default_factory=list)

    # Extracted data
    entities: ExtractedEntities = field(default_factory=ExtractedEntities)
    insights: List[ExtractedInsight] = field(default_factory=list)
    quotes: List[ExtractedQuote] = field(default_factory=list)
    facts: List[ExtractedFact] = field(default_factory=list)

    # AI Analysis (per provider) - compatibility with v2
    analyses: Dict[str, dict] = field(default_factory=dict)
    primary_analysis_provider: Optional[str] = None

    # Personal data
    notes: List[PersonalNote] = field(default_factory=list)
    is_starred: bool = False
    tags: List[str] = field(default_factory=list)

    # Connections to other videos
    connections: List[VideoConnection] = field(default_factory=list)
    shared_topics: List[str] = field(default_factory=list)

    # Q&A history (v2 compatibility)
    qa_history: List[dict] = field(default_factory=list)

    # Resources (v2 compatibility)
    resources: Optional[dict] = None

    # Costs tracking (v2 compatibility)
    costs: dict = field(default_factory=lambda: {'entries': [], 'total_cost': 0.0})

    # Metadata
    created_at: str = ""
    processed_at: str = ""
    last_accessed: str = ""
    extraction_status: str = "pending"  # pending, in_progress, complete

    def __post_init__(self):
        now = datetime.now().isoformat()
        if not self.created_at:
            self.created_at = now
        if not self.processed_at:
            self.processed_at = now
        if not self.last_accessed:
            self.last_accessed = now

    def to_dict(self) -> dict:
        """Convert to dictionary for JSON storage."""
        return {
            'video_id': self.video_id,
            'title': self.title,
            'url': self.url,
            'channel': self.channel,
            'duration': self.duration,
            'thumbnail': self.thumbnail,
            'file_path': self.file_path,
            'transcription': self.transcription,
            'language': self.language,
            'text_direction': self.text_direction,
            'sections': [s.to_dict() for s in self.sections],
            'entities': self.entities.to_dict(),
            'insights': [i.to_dict() for i in self.insights],
            'quotes': [q.to_dict() for q in self.quotes],
            'facts': [f.to_dict() for f in self.facts],
            'analyses': self.analyses,
            'primary_analysis_provider': self.primary_analysis_provider,
            'notes': [n.to_dict() for n in self.notes],
            'is_starred': self.is_starred,
            'tags': self.tags,
            'connections': [c.to_dict() for c in self.connections],
            'shared_topics': self.shared_topics,
            'qa_history': self.qa_history,
            'resources': self.resources,
            'costs': self.costs,
            'created_at': self.created_at,
            'processed_at': self.processed_at,
            'last_accessed': self.last_accessed,
            'extraction_status': self.extraction_status
        }

    @classmethod
    def from_dict(cls, data: dict) -> 'ProjectData':
        """Create from dictionary (JSON storage)."""
        return cls(
            video_id=data['video_id'],
            title=data.get('title', ''),
            url=data.get('url', ''),
            channel=data.get('channel', ''),
            duration=data.get('duration', 0),
            thumbnail=data.get('thumbnail', ''),
            file_path=data.get('file_path', ''),
            transcription=data.get('transcription', ''),
            language=data.get('language', ''),
            text_direction=data.get('text_direction', 'ltr'),
            sections=[SemanticSection.from_dict(s) for s in data.get('sections', [])],
            entities=ExtractedEntities.from_dict(data.get('entities', {})),
            insights=[ExtractedInsight.from_dict(i) for i in data.get('insights', [])],
            quotes=[ExtractedQuote.from_dict(q) for q in data.get('quotes', [])],
            facts=[ExtractedFact.from_dict(f) for f in data.get('facts', [])],
            analyses=data.get('analyses', {}),
            primary_analysis_provider=data.get('primary_analysis_provider'),
            notes=[PersonalNote.from_dict(n) for n in data.get('notes', [])],
            is_starred=data.get('is_starred', False),
            tags=data.get('tags', []),
            connections=[VideoConnection.from_dict(c) for c in data.get('connections', [])],
            shared_topics=data.get('shared_topics', []),
            qa_history=data.get('qa_history', []),
            resources=data.get('resources'),
            costs=data.get('costs', {'entries': [], 'total_cost': 0.0}),
            created_at=data.get('created_at', ''),
            processed_at=data.get('processed_at', ''),
            last_accessed=data.get('last_accessed', ''),
            extraction_status=data.get('extraction_status', 'pending')
        )


# =============================================================================
# Migration: v2 History Item -> v3 Project
# =============================================================================

def migrate_history_item_to_project(item: dict, transcription_text: str = "") -> ProjectData:
    """
    Migrate a v2 history item to v3 ProjectData format.

    Args:
        item: The v2 history item dict
        transcription_text: The transcription text (if loaded separately)

    Returns:
        ProjectData in v3 format
    """
    # Parse duration string to seconds
    duration_str = item.get('duration', '')
    duration_seconds = 0
    if duration_str:
        parts = duration_str.split(':')
        try:
            if len(parts) == 3:
                duration_seconds = int(parts[0]) * 3600 + int(parts[1]) * 60 + int(parts[2])
            elif len(parts) == 2:
                duration_seconds = int(parts[0]) * 60 + int(parts[1])
            elif len(parts) == 1:
                duration_seconds = int(parts[0])
        except ValueError:
            pass

    # Migrate analysis format
    analysis_data = item.get('analysis')
    analyses = {}
    primary_provider = None

    if analysis_data:
        if isinstance(analysis_data, dict) and 'analyses' in analysis_data:
            # Already in new format
            analyses = analysis_data.get('analyses', {})
            primary_provider = analysis_data.get('primary_provider')
        elif isinstance(analysis_data, dict):
            # Old format: single analysis dict
            provider = analysis_data.get('provider', 'unknown')
            analyses[provider] = {
                'content': analysis_data,
                'provider': provider,
                'timestamp': item.get('analyzed_at', item.get('created_at', ''))
            }
            primary_provider = provider
        elif isinstance(analysis_data, str):
            # Very old format: just a string
            analyses['unknown'] = {
                'content': {'analysis': analysis_data},
                'provider': 'unknown',
                'timestamp': item.get('created_at', '')
            }
            primary_provider = 'unknown'

    return ProjectData(
        video_id=item.get('id', ''),
        title=item.get('title', ''),
        url=item.get('url', ''),
        channel=item.get('channel', ''),
        duration=duration_seconds,
        thumbnail=item.get('thumbnail', ''),
        file_path=item.get('file_path', ''),
        transcription=transcription_text,
        language=item.get('language', ''),
        text_direction=item.get('text_direction', 'ltr'),
        analyses=analyses,
        primary_analysis_provider=primary_provider,
        qa_history=item.get('qa_history', []),
        resources=item.get('resources'),
        costs=item.get('costs', {'entries': [], 'total_cost': 0.0}),
        created_at=item.get('created_at', ''),
        processed_at=item.get('created_at', ''),
        extraction_status='pending'  # Will need extraction
    )


# =============================================================================
# Storage Functions
# =============================================================================

def get_projects_file() -> Path:
    """Get the projects file path."""
    return get_config_dir() / "projects.json"


def load_projects() -> List[dict]:
    """Load all projects from file."""
    projects_file = get_projects_file()
    if not projects_file.exists():
        return []

    try:
        with open(projects_file, 'r', encoding='utf-8') as f:
            return json.load(f)
    except (json.JSONDecodeError, Exception):
        return []


def save_projects(projects: List[dict]) -> None:
    """Save projects to file."""
    projects_file = get_projects_file()
    with open(projects_file, 'w', encoding='utf-8') as f:
        json.dump(projects, f, indent=2, ensure_ascii=False)


def get_project(video_id: str) -> Optional[ProjectData]:
    """Get a project by video ID."""
    projects = load_projects()
    for p in projects:
        if p.get('video_id') == video_id:
            return ProjectData.from_dict(p)
    return None


def save_project(project: ProjectData) -> ProjectData:
    """Save or update a project."""
    projects = load_projects()

    # Update last accessed
    project.last_accessed = datetime.now().isoformat()

    # Find and update or add
    found = False
    for i, p in enumerate(projects):
        if p.get('video_id') == project.video_id:
            projects[i] = project.to_dict()
            found = True
            break

    if not found:
        projects.insert(0, project.to_dict())

    save_projects(projects)
    return project


def delete_project(video_id: str) -> bool:
    """Delete a project by video ID."""
    projects = load_projects()
    original_len = len(projects)
    projects = [p for p in projects if p.get('video_id') != video_id]

    if len(projects) < original_len:
        save_projects(projects)
        return True
    return False


# =============================================================================
# Personal Notes Management
# =============================================================================

def add_note_to_project(
    video_id: str,
    content: str,
    timestamp: Optional[float] = None,
    tags: List[str] = None
) -> Optional[PersonalNote]:
    """Add a personal note to a project."""
    project = get_project(video_id)
    if not project:
        return None

    note = PersonalNote(
        id=str(uuid.uuid4()),
        content=content,
        timestamp=timestamp,
        tags=tags or []
    )

    project.notes.append(note)
    save_project(project)
    return note


def update_note(video_id: str, note_id: str, content: str, tags: List[str] = None) -> Optional[PersonalNote]:
    """Update an existing note."""
    project = get_project(video_id)
    if not project:
        return None

    for note in project.notes:
        if note.id == note_id:
            note.content = content
            note.updated_at = datetime.now().isoformat()
            if tags is not None:
                note.tags = tags
            save_project(project)
            return note

    return None


def delete_note(video_id: str, note_id: str) -> bool:
    """Delete a note from a project."""
    project = get_project(video_id)
    if not project:
        return False

    original_len = len(project.notes)
    project.notes = [n for n in project.notes if n.id != note_id]

    if len(project.notes) < original_len:
        save_project(project)
        return True
    return False


# =============================================================================
# Star/Tag Management
# =============================================================================

def toggle_star(video_id: str) -> Optional[bool]:
    """Toggle star status for a project. Returns new status."""
    project = get_project(video_id)
    if not project:
        return None

    project.is_starred = not project.is_starred
    save_project(project)
    return project.is_starred


def update_tags(video_id: str, tags: List[str]) -> Optional[List[str]]:
    """Update tags for a project."""
    project = get_project(video_id)
    if not project:
        return None

    project.tags = tags
    save_project(project)
    return project.tags


# =============================================================================
# Dashboard Statistics
# =============================================================================

def get_dashboard_stats() -> dict:
    """Get statistics for the dashboard."""
    projects = load_projects()

    total_videos = len(projects)
    total_insights = 0
    total_entities = 0
    total_notes = 0
    total_quotes = 0
    total_facts = 0
    total_duration_seconds = 0

    # Extraction status counts
    extracted_count = 0
    processing_count = 0
    pending_count = 0

    # Starred count
    starred_count = 0

    # Duration breakdown
    short_videos = 0      # < 5 min
    medium_videos = 0     # 5-15 min
    long_videos = 0       # 15-60 min
    very_long_videos = 0  # 60+ min

    # Connection count
    total_connections = 0

    all_topics = set()

    for p in projects:
        project = ProjectData.from_dict(p)
        total_insights += len(project.insights)
        total_entities += project.entities.count()
        total_notes += len(project.notes)
        total_quotes += len(project.quotes)
        total_facts += len(project.facts)
        total_duration_seconds += project.duration
        all_topics.update(project.shared_topics)
        all_topics.update(project.tags)

        # Extraction status
        if project.extraction_status == 'complete':
            extracted_count += 1
        elif project.extraction_status == 'processing':
            processing_count += 1
        else:
            pending_count += 1

        # Starred
        if project.is_starred:
            starred_count += 1

        # Duration buckets (in seconds)
        duration = project.duration
        if duration < 300:  # < 5 min
            short_videos += 1
        elif duration < 900:  # 5-15 min
            medium_videos += 1
        elif duration < 3600:  # 15-60 min
            long_videos += 1
        else:  # 60+ min
            very_long_videos += 1

        # Connection count (related videos)
        total_connections += len(project.connections)

    return {
        'total_videos': total_videos,
        'total_insights': total_insights,
        'total_entities': total_entities,
        'total_notes': total_notes,
        'total_quotes': total_quotes,
        'total_facts': total_facts,
        'total_topics': len(all_topics),
        'total_watch_time_seconds': total_duration_seconds,
        'total_watch_time_formatted': format_duration(total_duration_seconds),
        'extracted_count': extracted_count,
        'processing_count': processing_count,
        'pending_count': pending_count,
        'starred_count': starred_count,
        'short_videos': short_videos,
        'medium_videos': medium_videos,
        'long_videos': long_videos,
        'very_long_videos': very_long_videos,
        'total_connections': total_connections
    }


def format_duration(seconds: int) -> str:
    """Format seconds into human-readable duration."""
    hours = seconds // 3600
    minutes = (seconds % 3600) // 60

    if hours > 0:
        return f"{hours}h {minutes}m"
    return f"{minutes}m"


# =============================================================================
# Recent Activity
# =============================================================================

def get_recent_activity(limit: int = 20) -> List[dict]:
    """Get recent activity across all projects."""
    projects = load_projects()

    activities = []

    for p in projects:
        # Add project creation as activity
        activities.append({
            'type': 'video_added',
            'video_id': p.get('video_id'),
            'title': p.get('title'),
            'timestamp': p.get('created_at', ''),
            'details': {}
        })

        # Add notes as activities
        for note in p.get('notes', []):
            activities.append({
                'type': 'note_added',
                'video_id': p.get('video_id'),
                'title': p.get('title'),
                'timestamp': note.get('created_at', ''),
                'details': {'note_preview': note.get('content', '')[:100]}
            })

        # Add extraction completion as activity
        if p.get('extraction_status') == 'complete':
            insight_count = len(p.get('insights', []))
            entity_count = sum(
                len(p.get('entities', {}).get(t, []))
                for t in ['people', 'organizations', 'technologies', 'products', 'places', 'concepts']
            )
            if insight_count > 0 or entity_count > 0:
                activities.append({
                    'type': 'extraction_complete',
                    'video_id': p.get('video_id'),
                    'title': p.get('title'),
                    'timestamp': p.get('processed_at', ''),
                    'details': {
                        'insights_count': insight_count,
                        'entities_count': entity_count
                    }
                })

    # Sort by timestamp descending
    activities.sort(key=lambda x: x.get('timestamp', ''), reverse=True)

    return activities[:limit]
