"""SQLAlchemy models for the Trax platform with Registry pattern.

This module defines all database models using the Registry pattern to prevent
SQLAlchemy "multiple classes" errors as specified in the project requirements.
"""

from datetime import datetime
from typing import Any, Dict
from uuid import uuid4

from sqlalchemy import (
    BigInteger,
    Column,
    DateTime,
    Float,
    ForeignKey,
    Integer,
    String,
    Text,
)
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import relationship

from . import Base, register_model


class TimestampedMixin:
    """Mixin for models with created_at and updated_at timestamps."""

    created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)


class VersionedMixin:
    """Mixin for models with version tracking."""

    version = Column(Integer, default=1, nullable=False)


@register_model
class YouTubeVideo(Base, TimestampedMixin):
    """YouTube video model for storing video metadata.

    Stores metadata extracted from YouTube URLs using curl.
    """

    __tablename__ = "youtube_videos"

    id = Column(PGUUID(as_uuid=True), primary_key=True, default=uuid4)
    youtube_id = Column(String(20), nullable=False, unique=True, index=True)
    title = Column(String(500), nullable=False)
    channel = Column(String(200), nullable=False)
    description = Column(Text, nullable=True)
    duration_seconds = Column(Integer, nullable=False)
    url = Column(String(500), nullable=False)
    metadata_extracted_at = Column(DateTime, default=datetime.utcnow)

    # Relationships
    media_files = relationship("MediaFile", back_populates="youtube_video")

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "id": str(self.id),
            "youtube_id": self.youtube_id,
            "title": self.title,
            "channel": self.channel,
            "description": self.description,
            "duration_seconds": self.duration_seconds,
            "url": self.url,
            "metadata_extracted_at": self.metadata_extracted_at.isoformat() if self.metadata_extracted_at else None,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


@register_model
class MediaFile(Base, TimestampedMixin):
    """Media file model with PostgreSQL JSONB support.

    Stores information about media files that need to be transcribed.
    """

    __tablename__ = "media_files"

    id = Column(PGUUID(as_uuid=True), primary_key=True, default=uuid4)
    filename = Column(String(255), nullable=False, index=True)
    file_size = Column(BigInteger, nullable=False)  # Use BigInteger for large files
    duration = Column(Float)  # Duration in seconds
    mime_type = Column(String(100))
    source_path = Column(Text, nullable=False)
    local_path = Column(Text)
    file_hash = Column(String(64), unique=True, index=True)
    file_metadata = Column(JSONB, default=dict)  # PostgreSQL JSONB for flexible metadata
    status = Column(String(20), nullable=False, index=True, default="pending")  # pending, downloading, processing, ready, failed

    # Foreign key to YouTube video (optional)
    youtube_video_id = Column(PGUUID(as_uuid=True), ForeignKey("youtube_videos.id"), nullable=True, index=True)

    # Relationships
    youtube_video = relationship("YouTubeVideo", back_populates="media_files")
    transcription_jobs = relationship("TranscriptionJob", back_populates="media_file")
    transcription_results = relationship("TranscriptionResult", back_populates="media_file")

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "id": str(self.id),
            "filename": self.filename,
            "file_size": self.file_size,
            "duration": self.duration,
            "mime_type": self.mime_type,
            "source_path": self.source_path,
            "local_path": self.local_path,
            "file_hash": self.file_hash,
            "file_metadata": self.file_metadata,
            "status": self.status,
            "youtube_video_id": str(self.youtube_video_id) if self.youtube_video_id else None,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


@register_model
class TranscriptionJob(Base, TimestampedMixin):
    """Transcription job model for tracking transcription requests.

    Represents a single transcription job for a media file.
    """

    __tablename__ = "transcription_jobs"

    id = Column(PGUUID(as_uuid=True), primary_key=True, default=uuid4)
    media_file_id = Column(
        PGUUID(as_uuid=True), ForeignKey("media_files.id"), nullable=False, index=True
    )
    status = Column(
        String(20), nullable=False, index=True, default="pending"
    )  # pending, processing, completed, failed
    priority = Column(Integer, default=0, index=True)  # Higher number = higher priority

    # Job configuration
    model_config = Column(JSONB, default=dict)  # Whisper model settings
    processing_options = Column(JSONB, default=dict)  # Processing options

    # Timing
    started_at = Column(DateTime)
    completed_at = Column(DateTime)
    processing_time = Column(Float)  # Total processing time in seconds

    # Error handling
    error_message = Column(Text)
    retry_count = Column(Integer, default=0)
    max_retries = Column(Integer, default=3)

    # Relationships
    media_file = relationship("MediaFile", back_populates="transcription_jobs")
    transcription_results = relationship("TranscriptionResult", back_populates="transcription_job")

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "id": str(self.id),
            "media_file_id": str(self.media_file_id),
            "status": self.status,
            "priority": self.priority,
            "model_config": self.model_config,
            "processing_options": self.processing_options,
            "started_at": self.started_at.isoformat() if self.started_at else None,
            "completed_at": self.completed_at.isoformat() if self.completed_at else None,
            "processing_time": self.processing_time,
            "error_message": self.error_message,
            "retry_count": self.retry_count,
            "max_retries": self.max_retries,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


@register_model
class TranscriptionResult(Base, TimestampedMixin, VersionedMixin):
    """Transcription result model with JSONB content storage.

    Stores the actual transcription results with versioning support.
    """

    __tablename__ = "transcription_results"

    id = Column(PGUUID(as_uuid=True), primary_key=True, default=uuid4)
    job_id = Column(
        PGUUID(as_uuid=True), ForeignKey("transcription_jobs.id"), nullable=False, index=True
    )
    media_file_id = Column(
        PGUUID(as_uuid=True), ForeignKey("media_files.id"), nullable=False, index=True
    )

    # Pipeline version tracking (v1, v2, v3, v4)
    pipeline_version = Column(String(10), nullable=False, default="v1", index=True)

    # Content storage using JSONB for flexibility
    content = Column(JSONB, nullable=False)  # Main transcription content
    segments = Column(JSONB)  # Individual segments with timestamps
    confidence_scores = Column(JSONB)  # Confidence scores for segments
    speaker_info = Column(JSONB)  # Speaker diarization information (v4)

    # Quality metrics
    accuracy = Column(Float)  # Overall accuracy score
    word_count = Column(Integer)
    processing_time = Column(Float)  # Processing time for this version

    # Model information
    model_used = Column(String(100))
    model_config = Column(JSONB)  # Model configuration used

    # Parent result for version tracking
    parent_result_id = Column(
        PGUUID(as_uuid=True), ForeignKey("transcription_results.id"), index=True
    )

    # V2-specific columns (nullable for backward compatibility)
    enhanced_content = Column(JSONB, nullable=True)  # Enhanced transcription content
    diarization_content = Column(JSONB, nullable=True)  # Speaker diarization data
    merged_content = Column(JSONB, nullable=True)  # Merged content from multiple sources
    domain_used = Column(String(100), nullable=True)  # Domain-specific processing
    accuracy_estimate = Column(Float, nullable=True)  # Estimated accuracy for v2
    speaker_count = Column(Integer, nullable=True)  # Number of speakers detected
    quality_warnings = Column(JSONB, nullable=True)  # Quality warnings and issues
    processing_metadata = Column(JSONB, nullable=True)  # Additional processing metadata

    # Relationships
    transcription_job = relationship("TranscriptionJob", back_populates="transcription_results")
    media_file = relationship("MediaFile", back_populates="transcription_results")
    parent_result = relationship("TranscriptionResult", remote_side=[id])
    child_results = relationship("TranscriptionResult", overlaps="parent_result")
    v2_processing_jobs = relationship("V2ProcessingJob", back_populates="transcript")

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "id": str(self.id),
            "job_id": str(self.job_id),
            "media_file_id": str(self.media_file_id),
            "pipeline_version": self.pipeline_version,
            "content": self.content,
            "segments": self.segments,
            "confidence_scores": self.confidence_scores,
            "speaker_info": self.speaker_info,
            "accuracy": self.accuracy,
            "word_count": self.word_count,
            "processing_time": self.processing_time,
            "model_used": self.model_used,
            "model_config": self.model_config,
            "parent_result_id": str(self.parent_result_id) if self.parent_result_id else None,
            "version": self.version,
            # V2 fields
            "enhanced_content": self.enhanced_content,
            "diarization_content": self.diarization_content,
            "merged_content": self.merged_content,
            "domain_used": self.domain_used,
            "accuracy_estimate": self.accuracy_estimate,
            "speaker_count": self.speaker_count,
            "quality_warnings": self.quality_warnings,
            "processing_metadata": self.processing_metadata,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


@register_model
class ProcessingJob(Base, TimestampedMixin):
    """Processing job model for tracking batch operations.

    Represents batch processing jobs that handle multiple files.
    """

    __tablename__ = "processing_jobs"

    id = Column(PGUUID(as_uuid=True), primary_key=True, default=uuid4)
    job_type = Column(String(50), nullable=False, index=True)  # transcription, enhancement, etc.
    status = Column(String(20), nullable=False, index=True, default="pending")

    # Batch configuration
    config = Column(JSONB, default=dict)  # Job configuration
    file_patterns = Column(JSONB)  # File patterns to process

    # Progress tracking
    total_items = Column(Integer, default=0)
    processed_items = Column(Integer, default=0)
    successful_items = Column(Integer, default=0)
    failed_items = Column(Integer, default=0)

    # Timing
    started_at = Column(DateTime)
    completed_at = Column(DateTime)

    # Error handling
    error_message = Column(Text)

    @property
    def progress_percentage(self) -> float:
        """Calculate progress percentage."""
        if self.total_items == 0:
            return 0.0
        return (self.processed_items / self.total_items) * 100

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "id": str(self.id),
            "job_type": self.job_type,
            "status": self.status,
            "config": self.config,
            "file_patterns": self.file_patterns,
            "progress_percentage": self.progress_percentage,
            "total_items": self.total_items,
            "processed_items": self.processed_items,
            "successful_items": self.successful_items,
            "failed_items": self.failed_items,
            "started_at": self.started_at.isoformat() if self.started_at else None,
            "completed_at": self.completed_at.isoformat() if self.completed_at else None,
            "error_message": self.error_message,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


@register_model
class SpeakerProfile(Base, TimestampedMixin):
    """Speaker profile model for v2 speaker diarization features.

    Stores speaker characteristics and embeddings for speaker identification.
    """

    __tablename__ = "speaker_profiles"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(255), nullable=False, index=True)
    characteristics = Column(JSONB, nullable=True)  # Voice characteristics
    embedding = Column(Text, nullable=True)  # Speaker embedding (base64 encoded)
    sample_count = Column(Integer, default=0)  # Number of samples for this speaker
    user_id = Column(Integer, nullable=True, index=True)  # Associated user (optional)

    # Relationships
    # Note: user relationship would be added when user model is implemented

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "id": self.id,
            "name": self.name,
            "characteristics": self.characteristics,
            "embedding": self.embedding,
            "sample_count": self.sample_count,
            "user_id": self.user_id,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


@register_model
class V2ProcessingJob(Base, TimestampedMixin):
    """V2 Processing job model for individual transcript processing.

    Represents individual processing jobs for transcripts (enhancement, diarization, etc.).
    """

    __tablename__ = "v2_processing_jobs"

    id = Column(Integer, primary_key=True, autoincrement=True)
    status = Column(String(50), nullable=False, default="pending", index=True)
    completed_at = Column(DateTime, nullable=True)
    transcript_id = Column(
        PGUUID(as_uuid=True), ForeignKey("transcription_results.id"), nullable=True, index=True
    )
    job_type = Column(String(50), nullable=False, index=True)  # enhancement, diarization, etc.
    parameters = Column(JSONB, nullable=True)  # Job parameters
    progress = Column(Float, default=0.0)  # Progress percentage (0.0 to 1.0)
    error_message = Column(Text, nullable=True)  # Error message if failed
    result_data = Column(JSONB, nullable=True)  # Job result data

    # Relationships
    transcript = relationship("TranscriptionResult", back_populates="v2_processing_jobs")

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "id": self.id,
            "status": self.status,
            "completed_at": self.completed_at.isoformat() if self.completed_at else None,
            "transcript_id": str(self.transcript_id) if self.transcript_id else None,
            "job_type": self.job_type,
            "parameters": self.parameters,
            "progress": self.progress,
            "error_message": self.error_message,
            "result_data": self.result_data,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }
