"""Standardized SQLite storage for media analysis and processing results with content hashing"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/storage.ipynb.

# %% auto #0
__all__ = ['MediaAnalysisRow', 'MediaAnalysisStorage', 'MediaProcessingRow', 'MediaProcessingStorage']

# %% ../nbs/storage.ipynb #cell-imports
import json
import sqlite3
import time
from dataclasses import dataclass
from typing import Any, Dict, List, Optional

from cjm_plugin_system.utils.hashing import hash_file

# %% ../nbs/storage.ipynb #cell-row-dataclass
@dataclass
class MediaAnalysisRow:
    """A single row from the analysis_jobs table."""
    file_path: str       # Path to the analyzed media file
    file_hash: str       # Hash of source file in "algo:hexdigest" format
    config_hash: str     # Hash of the analysis config used
    ranges: Optional[List[Dict[str, Any]]] = None  # Detected temporal segments
    metadata: Optional[Dict[str, Any]] = None       # Analysis metadata
    created_at: Optional[float] = None               # Unix timestamp

# %% ../nbs/storage.ipynb #cell-storage-class
class MediaAnalysisStorage:
    """Standardized SQLite storage for media analysis results."""

    SCHEMA = """
        CREATE TABLE IF NOT EXISTS analysis_jobs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            file_path TEXT NOT NULL,
            file_hash TEXT NOT NULL,
            config_hash TEXT NOT NULL,
            ranges JSON,
            metadata JSON,
            created_at REAL NOT NULL,
            UNIQUE(file_path, config_hash)
        )
    """

    INDEX = "CREATE INDEX IF NOT EXISTS idx_analysis_file_path ON analysis_jobs(file_path);"

    def __init__(
        self,
        db_path: str  # Absolute path to the SQLite database file
    ):
        """Initialize storage and create table if needed."""
        self.db_path = db_path
        with sqlite3.connect(self.db_path) as con:
            con.execute(self.SCHEMA)
            con.execute(self.INDEX)

    def save(
        self,
        file_path: str,     # Path to the analyzed media file
        file_hash: str,     # Hash of source file in "algo:hexdigest" format
        config_hash: str,   # Hash of the analysis config
        ranges: Optional[List[Dict[str, Any]]] = None,  # Detected temporal segments
        metadata: Optional[Dict[str, Any]] = None        # Analysis metadata
    ) -> None:
        """Save or replace an analysis result (upsert by file_path + config_hash)."""
        with sqlite3.connect(self.db_path) as con:
            con.execute(
                """INSERT OR REPLACE INTO analysis_jobs
                   (file_path, file_hash, config_hash, ranges, metadata, created_at)
                   VALUES (?, ?, ?, ?, ?, ?)""",
                (
                    file_path,
                    file_hash,
                    config_hash,
                    json.dumps(ranges) if ranges else None,
                    json.dumps(metadata) if metadata else None,
                    time.time()
                )
            )

    def get_cached(
        self,
        file_path: str,   # Path to the media file
        config_hash: str  # Config hash to match
    ) -> Optional[MediaAnalysisRow]:  # Cached row or None
        """Retrieve a cached analysis result by file path and config hash."""
        with sqlite3.connect(self.db_path) as con:
            cur = con.execute(
                """SELECT file_path, file_hash, config_hash, ranges, metadata, created_at
                   FROM analysis_jobs WHERE file_path = ? AND config_hash = ?""",
                (file_path, config_hash)
            )
            row = cur.fetchone()
            if not row:
                return None
            return MediaAnalysisRow(
                file_path=row[0],
                file_hash=row[1],
                config_hash=row[2],
                ranges=json.loads(row[3]) if row[3] else None,
                metadata=json.loads(row[4]) if row[4] else None,
                created_at=row[5]
            )

    def list_jobs(
        self,
        limit: int = 100  # Maximum number of rows to return
    ) -> List[MediaAnalysisRow]:  # List of analysis rows
        """List analysis jobs ordered by creation time (newest first)."""
        results = []
        with sqlite3.connect(self.db_path) as con:
            cur = con.execute(
                """SELECT file_path, file_hash, config_hash, ranges, metadata, created_at
                   FROM analysis_jobs ORDER BY created_at DESC LIMIT ?""",
                (limit,)
            )
            for row in cur:
                results.append(MediaAnalysisRow(
                    file_path=row[0],
                    file_hash=row[1],
                    config_hash=row[2],
                    ranges=json.loads(row[3]) if row[3] else None,
                    metadata=json.loads(row[4]) if row[4] else None,
                    created_at=row[5]
                ))
        return results

    def verify_file(
        self,
        file_path: str,   # Path to the media file
        config_hash: str  # Config hash to look up
    ) -> Optional[bool]:  # True if file matches, False if changed, None if not found
        """Verify the source media file still matches its stored hash."""
        row = self.get_cached(file_path, config_hash)
        if not row:
            return None
        current_hash = hash_file(row.file_path)
        return current_hash == row.file_hash

# %% ../nbs/storage.ipynb #66ktl2o719
@dataclass
class MediaProcessingRow:
    """A single row from the processing_jobs table."""
    job_id: str          # Unique job identifier
    action: str          # Operation performed: 'convert', 'extract_segment', etc.
    input_path: str      # Path to the source media file
    input_hash: str      # Hash of source file in "algo:hexdigest" format
    output_path: str     # Path to the produced output file
    output_hash: str     # Hash of output file in "algo:hexdigest" format
    parameters: Optional[Dict[str, Any]] = None  # Action-specific parameters
    metadata: Optional[Dict[str, Any]] = None     # Processing metadata
    created_at: Optional[float] = None             # Unix timestamp

# %% ../nbs/storage.ipynb #4wdlaqv0ay9
class MediaProcessingStorage:
    """Standardized SQLite storage for media processing results."""

    SCHEMA = """
        CREATE TABLE IF NOT EXISTS processing_jobs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            job_id TEXT UNIQUE NOT NULL,
            action TEXT NOT NULL,
            input_path TEXT NOT NULL,
            input_hash TEXT NOT NULL,
            output_path TEXT NOT NULL,
            output_hash TEXT NOT NULL,
            parameters JSON,
            metadata JSON,
            created_at REAL NOT NULL
        )
    """

    INDEX = "CREATE INDEX IF NOT EXISTS idx_processing_job_id ON processing_jobs(job_id);"

    def __init__(
        self,
        db_path: str  # Absolute path to the SQLite database file
    ):
        """Initialize storage and create table if needed."""
        self.db_path = db_path
        with sqlite3.connect(self.db_path) as con:
            con.execute(self.SCHEMA)
            con.execute(self.INDEX)

    def save(
        self,
        job_id: str,        # Unique job identifier
        action: str,        # Operation performed: 'convert', 'extract_segment', etc.
        input_path: str,    # Path to the source media file
        input_hash: str,    # Hash of source file in "algo:hexdigest" format
        output_path: str,   # Path to the produced output file
        output_hash: str,   # Hash of output file in "algo:hexdigest" format
        parameters: Optional[Dict[str, Any]] = None,  # Action-specific parameters
        metadata: Optional[Dict[str, Any]] = None       # Processing metadata
    ) -> None:
        """Save a media processing result to the database."""
        with sqlite3.connect(self.db_path) as con:
            con.execute(
                """INSERT INTO processing_jobs
                   (job_id, action, input_path, input_hash, output_path, output_hash,
                    parameters, metadata, created_at)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                (
                    job_id,
                    action,
                    input_path,
                    input_hash,
                    output_path,
                    output_hash,
                    json.dumps(parameters) if parameters else None,
                    json.dumps(metadata) if metadata else None,
                    time.time()
                )
            )

    def get_by_job_id(
        self,
        job_id: str  # Job identifier to look up
    ) -> Optional[MediaProcessingRow]:  # Row or None if not found
        """Retrieve a processing result by job ID."""
        with sqlite3.connect(self.db_path) as con:
            cur = con.execute(
                """SELECT job_id, action, input_path, input_hash, output_path, output_hash,
                          parameters, metadata, created_at
                   FROM processing_jobs WHERE job_id = ?""",
                (job_id,)
            )
            row = cur.fetchone()
            if not row:
                return None
            return MediaProcessingRow(
                job_id=row[0],
                action=row[1],
                input_path=row[2],
                input_hash=row[3],
                output_path=row[4],
                output_hash=row[5],
                parameters=json.loads(row[6]) if row[6] else None,
                metadata=json.loads(row[7]) if row[7] else None,
                created_at=row[8]
            )

    def list_jobs(
        self,
        limit: int = 100  # Maximum number of rows to return
    ) -> List[MediaProcessingRow]:  # List of processing rows
        """List processing jobs ordered by creation time (newest first)."""
        results = []
        with sqlite3.connect(self.db_path) as con:
            cur = con.execute(
                """SELECT job_id, action, input_path, input_hash, output_path, output_hash,
                          parameters, metadata, created_at
                   FROM processing_jobs ORDER BY created_at DESC LIMIT ?""",
                (limit,)
            )
            for row in cur:
                results.append(MediaProcessingRow(
                    job_id=row[0],
                    action=row[1],
                    input_path=row[2],
                    input_hash=row[3],
                    output_path=row[4],
                    output_hash=row[5],
                    parameters=json.loads(row[6]) if row[6] else None,
                    metadata=json.loads(row[7]) if row[7] else None,
                    created_at=row[8]
                ))
        return results

    def verify_input(
        self,
        job_id: str  # Job identifier to verify
    ) -> Optional[bool]:  # True if input matches, False if changed, None if not found
        """Verify the source media file still matches its stored hash."""
        row = self.get_by_job_id(job_id)
        if not row:
            return None
        current_hash = hash_file(row.input_path)
        return current_hash == row.input_hash

    def verify_output(
        self,
        job_id: str  # Job identifier to verify
    ) -> Optional[bool]:  # True if output matches, False if changed, None if not found
        """Verify the output media file still matches its stored hash."""
        row = self.get_by_job_id(job_id)
        if not row:
            return None
        current_hash = hash_file(row.output_path)
        return current_hash == row.output_hash
