import subprocess
from datetime import datetime, timezone
from typing import Optional

from celery import Celery
from celery.app.task import Task
from celery.utils.log import get_task_logger
from worker_app.celery_app import celery_app
from worker_app.core.asset_types import infer_mime_type_from_storage_key
from worker_app.db import SessionLocal

from packages.adapters.sqlalchemy_impl import (
    SQLAlchemyAssetRepository,
    SQLAlchemyIngestJobRepository,
)
from packages.domain import Asset, AssetStatus, IngestJobStatus

logger = get_task_logger(__name__)


def _safe_parse_fps(fps_str: str) -> float:
    """Safely parse fps from a fraction string like \"30/1\" or \"30000/1001\"."""
    try:
        if "/" in fps_str:
            num, den = fps_str.split("/", 1)
            den_val = float(den)
            if den_val == 0:
                return 0.0
            return float(num) / den_val
        return float(fps_str)
    except (ValueError, ZeroDivisionError):
        return 0.0


def extract_media_metadata(file_url: str, media_type: str) -> dict:
    """
    提取媒体文件的元数据。

    Args:
        file_url: 媒体文件 URL 或本地路径
        media_type: 媒体类型 (video, audio, image)

    Returns:
        提取的元数据字典，失败时返回空字典
    """
    metadata = {}

    try:
        if media_type == "video":
            # 使用 ffprobe 提取视频元数据
            cmd = [
                "ffprobe",
                "-v",
                "quiet",
                "-print_format",
                "json",
                "-show_format",
                "-show_streams",
                file_url,
            ]
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=30,
            )
            if result.returncode == 0:
                import json as json_lib

                probe_data = json_lib.loads(result.stdout)

                # 提取视频流信息
                for stream in probe_data.get("streams", []):
                    if stream.get("codec_type") == "video":
                        metadata["width"] = int(stream.get("width", 0))
                        metadata["height"] = int(stream.get("height", 0))
                        metadata["codec"] = stream.get("codec_name", "")
                        metadata["fps"] = (
                            _safe_parse_fps(stream.get("r_frame_rate", "0/1")) if stream.get("r_frame_rate") else 0
                        )
                        break

                # 提取格式信息
                format_info = probe_data.get("format", {})
                metadata["duration"] = float(format_info.get("duration", 0))
                metadata["size_bytes"] = int(format_info.get("size", 0))
                metadata["bitrate"] = int(format_info.get("bit_rate", 0))

        elif media_type == "image":
            # 使用 Pillow 提取图片元数据
            try:
                from PIL import Image

                with Image.open(file_url) as img:
                    metadata["width"] = img.width
                    metadata["height"] = img.height
                    metadata["format"] = img.format
                    metadata["mode"] = img.mode
                    if hasattr(img, "_getexif") and img._getexif():
                        exif = img._getexif()
                        if exif:
                            metadata["exif"] = {k: str(v) for k, v in exif.items() if isinstance(v, (str, int, float))}
            except ImportError:
                logger.warning("Pillow not available for image metadata extraction")
            except Exception as e:
                logger.warning(f"Failed to extract image metadata: {e}")

    except subprocess.TimeoutExpired:
        logger.warning(f"Timeout extracting metadata from {file_url}")
    except FileNotFoundError:
        logger.warning("ffprobe not found, cannot extract video metadata")
    except Exception as e:
        logger.warning(f"Failed to extract metadata: {e}")

    return metadata


@celery_app.task(name="worker.ingest_asset")
def ingest_asset(job_id: str) -> dict:
    """
    Ingest asset task.

    Steps:
    1. Fetch IngestJob from repository
    2. Extract metadata from storage_key
    3. Create Asset entity
    4. Update IngestJob status to COMPLETED
    5. Return result
    """
    db = SessionLocal()
    try:
        job_repo = SQLAlchemyIngestJobRepository(db)
        asset_repo = SQLAlchemyAssetRepository(db)

        job = job_repo.get(job_id)
        if job is None:
            return {"status": "failed", "error": "job not found"}

        # Update job status to PROCESSING
        job.status = IngestJobStatus.PROCESSING
        job.updated_at = datetime.now(timezone.utc)
        job_repo.update(job)
        db.commit()

        # Extract real metadata from media file
        filename = job.storage_key.split("/")[-1]
        mime_type = infer_mime_type_from_storage_key(job.storage_key)

        # Determine media type from mime_type
        media_type = "video"
        if mime_type.startswith("image/"):
            media_type = "image"
        elif mime_type.startswith("audio/"):
            media_type = "audio"

        # Extract metadata (returns empty dict on failure)
        storage_url = job.storage_key  # Assuming storage_key is usable as URL/path
        metadata = extract_media_metadata(storage_url, media_type)

        # Fill in defaults if metadata extraction failed
        if not metadata:
            metadata = {
                "duration": 0,
                "width": 0,
                "height": 0,
                "size_bytes": 0,
            }

        # Create Asset
        asset = Asset.create(
            project_id=job.project_id,
            library_id=job.library_id,
            name=filename,
            storage_key=job.storage_key,
            mime_type=mime_type,
            metadata=metadata,
            file_size=int(metadata.get("size_bytes", 0)),
            duration=float(metadata.get("duration", 0)),
            width=int(metadata.get("width", 0)),
            height=int(metadata.get("height", 0)),
            status=AssetStatus.READY,
            file_hash=job.file_hash,
        )
        asset_repo.create(asset)

        # Update job status to COMPLETED
        job.status = IngestJobStatus.COMPLETED
        job.result_asset_id = asset.id
        job.updated_at = datetime.now(timezone.utc)
        job_repo.update(job)

        db.commit()

        return {
            "status": "completed",
            "job_id": job.id,
            "asset_id": asset.id,
        }
    except Exception as e:
        db.rollback()
        logger.error(f"Failed to ingest asset {job_id}: {e}")

        # Update job status to FAILED
        try:
            job_repo = SQLAlchemyIngestJobRepository(db)
            job = job_repo.get(job_id)
            if job:
                job.status = IngestJobStatus.FAILED
                job.error_message = str(e)
                job.updated_at = datetime.now(timezone.utc)
                job_repo.update(job)
                db.commit()
        except Exception:
            db.rollback()

        return {
            "status": "failed",
            "job_id": job_id,
            "error": str(e),
        }
    finally:
        db.close()
