"""Database utility functions for common operations and JSONB queries.

This module provides utility functions for common database operations,
JSONB query helpers, and database maintenance tasks.
"""

import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from sqlalchemy import and_, func, text
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session

from .models import MediaFile, TranscriptionJob, TranscriptionResult

logger = logging.getLogger(__name__)


def create_tables():
    """Create all database tables.

    This function creates all tables defined in the models.
    It should be called after the database engine is configured.
    """
    from . import Base
    from .connection import get_engine

    engine = get_engine()
    Base.metadata.create_all(bind=engine)
    logger.info("All database tables created")


def drop_tables():
    """Drop all database tables.

    Warning: This will delete all data in the database.
    """
    from . import Base
    from .connection import get_engine

    engine = get_engine()
    Base.metadata.drop_all(bind=engine)
    logger.warning("All database tables dropped")


def get_table_info() -> Dict[str, Any]:
    """Get information about all tables in the database.

    Returns:
        Dictionary with table information

    """
    from . import Base
    from .connection import get_engine

    get_engine()
    metadata = Base.metadata

    table_info = {}
    for table_name, table in metadata.tables.items():
        table_info[table_name] = {
            "columns": [col.name for col in table.columns],
            "indexes": [idx.name for idx in table.indexes],
            "foreign_keys": [fk.name for fk in table.foreign_keys],
        }

    return table_info


# JSONB Query Helpers


def jsonb_contains(session: Session, model_class, column: str, value: Dict[str, Any]) -> List[Any]:
    """Query for records where JSONB column contains the specified value.

    Args:
        session: Database session
        model_class: Model class to query
        column: JSONB column name
        value: Value to search for in JSONB

    Returns:
        List of matching records

    """
    column_attr = getattr(model_class, column)
    return session.query(model_class).filter(column_attr.contains(value)).all()


def jsonb_path_exists(session: Session, model_class, column: str, path: str) -> List[Any]:
    """Query for records where JSONB column has a specific path.

    Args:
        session: Database session
        model_class: Model class to query
        column: JSONB column name
        path: JSON path to check (e.g., '$.metadata.quality')

    Returns:
        List of matching records

    """
    column_attr = getattr(model_class, column)
    return session.query(model_class).filter(func.jsonb_path_exists(column_attr, path)).all()


def jsonb_extract_text(session: Session, model_class, column: str, path: str) -> List[Any]:
    """Query for records and extract text from JSONB path.

    Args:
        session: Database session
        model_class: Model class to query
        column: JSONB column name
        path: JSON path to extract (e.g., '$.metadata.title')

    Returns:
        List of records with extracted text

    """
    column_attr = getattr(model_class, column)
    return session.query(
        model_class, func.jsonb_extract_path_text(column_attr, path).label("extracted_text")
    ).all()


# Media File Utilities


def find_media_file_by_hash(session: Session, file_hash: str) -> Optional[MediaFile]:
    """Find media file by its hash.

    Args:
        session: Database session
        file_hash: SHA256 hash of the file

    Returns:
        MediaFile if found, None otherwise

    """
    return session.query(MediaFile).filter(MediaFile.file_hash == file_hash).first()


def find_media_files_by_pattern(session: Session, pattern: str) -> List[MediaFile]:
    """Find media files by filename pattern.

    Args:
        session: Database session
        pattern: SQL LIKE pattern for filename

    Returns:
        List of matching MediaFile records

    """
    return session.query(MediaFile).filter(MediaFile.filename.like(pattern)).all()


def update_media_file_metadata(
    session: Session, media_file_id: str, metadata: Dict[str, Any]
) -> Optional[MediaFile]:
    """Update media file metadata by merging with existing metadata.

    Args:
        session: Database session
        media_file_id: Media file ID
        metadata: New metadata to merge

    Returns:
        Updated MediaFile if found, None otherwise

    """
    media_file = session.query(MediaFile).filter(MediaFile.id == media_file_id).first()
    if media_file:
        current_metadata = media_file.file_metadata or {}
        current_metadata.update(metadata)
        media_file.file_metadata = current_metadata
        media_file.updated_at = datetime.now(timezone.utc)
        session.commit()
    return media_file


# Transcription Job Utilities


def find_pending_jobs(session: Session, limit: Optional[int] = None) -> List[TranscriptionJob]:
    """Find pending transcription jobs.

    Args:
        session: Database session
        limit: Maximum number of jobs to return

    Returns:
        List of pending TranscriptionJob records

    """
    query = (
        session.query(TranscriptionJob)
        .filter(TranscriptionJob.status == "pending")
        .order_by(TranscriptionJob.priority.desc(), TranscriptionJob.created_at.asc())
    )

    if limit:
        query = query.limit(limit)

    return query.all()


def find_failed_jobs(session: Session, retry_limit: int = 3) -> List[TranscriptionJob]:
    """Find failed jobs that can be retried.

    Args:
        session: Database session
        retry_limit: Maximum number of retries allowed

    Returns:
        List of failed TranscriptionJob records that can be retried

    """
    return (
        session.query(TranscriptionJob)
        .filter(
            and_(TranscriptionJob.status == "failed", TranscriptionJob.retry_count < retry_limit)
        )
        .all()
    )


def update_job_status(
    session: Session, job_id: str, status: str, error_message: Optional[str] = None
) -> Optional[TranscriptionJob]:
    """Update transcription job status.

    Args:
        session: Database session
        job_id: Job ID
        status: New status
        error_message: Error message if status is failed

    Returns:
        Updated TranscriptionJob if found, None otherwise

    """
    job = session.query(TranscriptionJob).filter(TranscriptionJob.id == job_id).first()
    if job:
        job.status = status
        job.updated_at = datetime.now(timezone.utc)

        if status == "processing":
            job.started_at = datetime.now(timezone.utc)
        elif status in ["completed", "failed"]:
            job.completed_at = datetime.now(timezone.utc)

        if error_message:
            job.error_message = error_message
            job.retry_count += 1

        session.commit()
    return job


# Transcription Result Utilities


def find_latest_result(
    session: Session, media_file_id: str, pipeline_version: Optional[str] = None
) -> Optional[TranscriptionResult]:
    """Find the latest transcription result for a media file.

    Args:
        session: Database session
        media_file_id: Media file ID
        pipeline_version: Optional pipeline version filter

    Returns:
        Latest TranscriptionResult if found, None otherwise

    """
    query = session.query(TranscriptionResult).filter(
        TranscriptionResult.media_file_id == media_file_id
    )

    if pipeline_version:
        query = query.filter(TranscriptionResult.pipeline_version == pipeline_version)

    return query.order_by(TranscriptionResult.created_at.desc()).first()


def find_results_by_version(session: Session, pipeline_version: str) -> List[TranscriptionResult]:
    """Find all transcription results for a specific pipeline version.

    Args:
        session: Database session
        pipeline_version: Pipeline version (v1, v2, v3, v4)

    Returns:
        List of TranscriptionResult records

    """
    return (
        session.query(TranscriptionResult)
        .filter(TranscriptionResult.pipeline_version == pipeline_version)
        .order_by(TranscriptionResult.created_at.desc())
        .all()
    )


def get_result_statistics(session: Session) -> Dict[str, Any]:
    """Get statistics about transcription results.

    Args:
        session: Database session

    Returns:
        Dictionary with statistics

    """
    stats = {}

    # Count by pipeline version
    version_counts = (
        session.query(TranscriptionResult.pipeline_version, func.count(TranscriptionResult.id))
        .group_by(TranscriptionResult.pipeline_version)
        .all()
    )

    stats["by_version"] = dict(version_counts)

    # Average accuracy by version
    accuracy_stats = (
        session.query(
            TranscriptionResult.pipeline_version,
            func.avg(TranscriptionResult.accuracy).label("avg_accuracy"),
            func.count(TranscriptionResult.id).label("count"),
        )
        .filter(TranscriptionResult.accuracy.isnot(None))
        .group_by(TranscriptionResult.pipeline_version)
        .all()
    )

    stats["accuracy_by_version"] = {
        row.pipeline_version: {
            "avg_accuracy": float(row.avg_accuracy) if row.avg_accuracy else None,
            "count": row.count,
        }
        for row in accuracy_stats
    }

    # Total processing time
    total_time = (
        session.query(func.sum(TranscriptionResult.processing_time))
        .filter(TranscriptionResult.processing_time.isnot(None))
        .scalar()
    )

    stats["total_processing_time"] = float(total_time) if total_time else 0.0

    return stats


# Database Maintenance


def cleanup_old_jobs(session: Session, days_old: int = 30) -> int:
    """Clean up old completed jobs.

    Args:
        session: Database session
        days_old: Delete jobs older than this many days

    Returns:
        Number of jobs deleted

    """
    cutoff_date = datetime.now(timezone.utc) - timedelta(days=days_old)

    deleted_count = (
        session.query(TranscriptionJob)
        .filter(
            and_(
                TranscriptionJob.status.in_(["completed", "failed"]),
                TranscriptionJob.updated_at < cutoff_date,
            )
        )
        .delete()
    )

    session.commit()
    logger.info(f"Cleaned up {deleted_count} old jobs")
    return deleted_count


def optimize_database():
    """Run database optimization commands.

    This function runs VACUUM and ANALYZE commands to optimize
    the database performance.
    """
    from .connection import get_engine

    engine = get_engine()

    try:
        with engine.connect() as conn:
            # VACUUM to reclaim storage and update statistics
            conn.execute(text("VACUUM ANALYZE"))
            conn.commit()
            logger.info("Database optimization completed")
    except SQLAlchemyError as e:
        logger.error(f"Database optimization failed: {e}")
        raise


def get_database_size() -> Dict[str, Any]:
    """Get database size information.

    Returns:
        Dictionary with database size information

    """
    from .connection import get_engine

    engine = get_engine()

    try:
        with engine.connect() as conn:
            # Get database size
            result = conn.execute(
                text(
                    """
                SELECT
                    pg_size_pretty(pg_database_size(current_database())) as size,
                    pg_database_size(current_database()) as size_bytes
            """
                )
            )
            db_size = result.fetchone()

            # Get table sizes
            result = conn.execute(
                text(
                    """
                SELECT
                    schemaname,
                    tablename,
                    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size,
                    pg_total_relation_size(schemaname||'.'||tablename) as size_bytes
                FROM pg_tables
                WHERE schemaname = 'public'
                ORDER BY size_bytes DESC
            """
                )
            )
            table_sizes = [dict(row) for row in result.fetchall()]

            return {
                "database_size": db_size.size,
                "database_size_bytes": db_size.size_bytes,
                "table_sizes": table_sizes,
            }
    except SQLAlchemyError as e:
        logger.error(f"Failed to get database size: {e}")
        return {}


# Missing import
from datetime import timedelta
