"""
Repository interfaces and SQLite implementation for dependency persistence.

Implements FR-6.1: Directed dependency edges between artifacts.
Mirrors the SQLiteArtifactRepository pattern for consistency.
"""

import sqlite3
import json
from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Optional
from datetime import datetime

from markitect.prompts.dependencies.models import DependencyEdge, EdgeType


class DependencyRepositoryError(Exception):
    """Base exception for dependency repository errors."""
    pass


class DuplicateDependencyError(DependencyRepositoryError):
    """Raised when attempting to create a duplicate dependency edge."""
    pass


# SQL Schema for dependency tables
DEPENDENCY_TABLES_SQL = """
CREATE TABLE IF NOT EXISTS prompt_dependencies (
    id TEXT PRIMARY KEY,
    source_artifact_id TEXT NOT NULL,
    target_artifact_id TEXT NOT NULL,
    run_id TEXT NOT NULL,
    edge_type TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(source_artifact_id, target_artifact_id, run_id)
);

CREATE INDEX IF NOT EXISTS idx_deps_source ON prompt_dependencies(source_artifact_id);
CREATE INDEX IF NOT EXISTS idx_deps_target ON prompt_dependencies(target_artifact_id);
CREATE INDEX IF NOT EXISTS idx_deps_run ON prompt_dependencies(run_id);
CREATE INDEX IF NOT EXISTS idx_deps_type ON prompt_dependencies(edge_type);
CREATE INDEX IF NOT EXISTS idx_deps_source_target ON prompt_dependencies(source_artifact_id, target_artifact_id);
"""


def initialize_dependency_tables(db_path: str) -> None:
    """
    Initialize the dependency-related database tables.

    Args:
        db_path: Path to the SQLite database file
    """
    db_dir = Path(db_path).parent
    if db_dir and not db_dir.exists():
        db_dir.mkdir(parents=True, exist_ok=True)

    conn = sqlite3.connect(db_path)
    try:
        conn.executescript(DEPENDENCY_TABLES_SQL)
        conn.commit()
    finally:
        conn.close()


class IDependencyRepository(ABC):
    """
    Abstract interface for dependency edge persistence.

    Implements FR-6.1: Directed dependency edges between artifacts.
    """

    @abstractmethod
    def create(self, edge: DependencyEdge) -> DependencyEdge:
        """
        Persist a new dependency edge.

        Args:
            edge: Dependency edge to create

        Returns:
            Created edge

        Raises:
            DuplicateDependencyError: If edge with same source+target+run exists
            DependencyRepositoryError: On other persistence errors
        """
        pass

    @abstractmethod
    def get_by_id(self, edge_id: str) -> Optional[DependencyEdge]:
        """
        Retrieve edge by ID.

        Args:
            edge_id: Edge identifier

        Returns:
            Edge if found, None otherwise
        """
        pass

    @abstractmethod
    def get_by_source(self, source_artifact_id: str) -> List[DependencyEdge]:
        """
        Get all edges originating from a source artifact.

        Args:
            source_artifact_id: Source artifact ID

        Returns:
            List of edges from this source
        """
        pass

    @abstractmethod
    def get_by_target(self, target_artifact_id: str) -> List[DependencyEdge]:
        """
        Get all edges pointing to a target artifact.

        Args:
            target_artifact_id: Target artifact ID

        Returns:
            List of edges to this target
        """
        pass

    @abstractmethod
    def get_by_run(self, run_id: str) -> List[DependencyEdge]:
        """
        Get all edges created by a specific run.

        Args:
            run_id: Run identifier

        Returns:
            List of edges from this run
        """
        pass

    @abstractmethod
    def get_by_type(self, edge_type: EdgeType) -> List[DependencyEdge]:
        """
        Get all edges of a specific type.

        Args:
            edge_type: Edge type to filter by

        Returns:
            List of edges of this type
        """
        pass

    @abstractmethod
    def get_all(self) -> List[DependencyEdge]:
        """
        Get all dependency edges.

        Returns:
            List of all edges
        """
        pass

    @abstractmethod
    def delete(self, edge_id: str) -> bool:
        """
        Delete a dependency edge.

        Args:
            edge_id: Edge identifier

        Returns:
            True if deleted, False if not found
        """
        pass

    @abstractmethod
    def delete_by_run(self, run_id: str) -> int:
        """
        Delete all edges created by a specific run.

        Args:
            run_id: Run identifier

        Returns:
            Number of edges deleted
        """
        pass


class SQLiteDependencyRepository(IDependencyRepository):
    """
    SQLite implementation of dependency repository.

    Provides persistent storage for dependency edges with query support
    by source, target, run, and edge type.
    """

    def __init__(self, db_path: str):
        """
        Initialize repository with database path.

        Args:
            db_path: Path to SQLite database file
        """
        self.db_path = db_path
        initialize_dependency_tables(db_path)

    def _get_connection(self) -> sqlite3.Connection:
        """Get a database connection."""
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        return conn

    def _row_to_edge(self, row: sqlite3.Row) -> DependencyEdge:
        """Convert database row to DependencyEdge instance."""
        return DependencyEdge(
            id=row["id"],
            source_artifact_id=row["source_artifact_id"],
            target_artifact_id=row["target_artifact_id"],
            run_id=row["run_id"],
            edge_type=EdgeType(row["edge_type"]),
            created_at=datetime.fromisoformat(row["created_at"]),
        )

    def create(self, edge: DependencyEdge) -> DependencyEdge:
        """
        Persist a new dependency edge.

        Args:
            edge: Dependency edge to create

        Returns:
            Created edge

        Raises:
            DuplicateDependencyError: If edge with same source+target+run exists
            DependencyRepositoryError: On other persistence errors
        """
        conn = self._get_connection()
        try:
            conn.execute(
                """
                INSERT INTO prompt_dependencies (
                    id, source_artifact_id, target_artifact_id,
                    run_id, edge_type, created_at
                ) VALUES (?, ?, ?, ?, ?, ?)
                """,
                (
                    edge.id,
                    edge.source_artifact_id,
                    edge.target_artifact_id,
                    edge.run_id,
                    edge.edge_type.value,
                    edge.created_at.isoformat(),
                ),
            )
            conn.commit()
            return edge
        except sqlite3.IntegrityError as e:
            if "UNIQUE constraint" in str(e):
                raise DuplicateDependencyError(
                    f"Dependency edge from '{edge.source_artifact_id}' to "
                    f"'{edge.target_artifact_id}' already exists for run '{edge.run_id}'"
                )
            raise DependencyRepositoryError(f"Database integrity error: {e}")
        except Exception as e:
            raise DependencyRepositoryError(f"Failed to create dependency edge: {e}")
        finally:
            conn.close()

    def get_by_id(self, edge_id: str) -> Optional[DependencyEdge]:
        """
        Retrieve edge by ID.

        Args:
            edge_id: Edge identifier

        Returns:
            Edge if found, None otherwise
        """
        conn = self._get_connection()
        try:
            cursor = conn.execute(
                "SELECT * FROM prompt_dependencies WHERE id = ?",
                (edge_id,),
            )
            row = cursor.fetchone()
            return self._row_to_edge(row) if row else None
        finally:
            conn.close()

    def get_by_source(self, source_artifact_id: str) -> List[DependencyEdge]:
        """
        Get all edges originating from a source artifact.

        Args:
            source_artifact_id: Source artifact ID

        Returns:
            List of edges from this source
        """
        conn = self._get_connection()
        try:
            cursor = conn.execute(
                "SELECT * FROM prompt_dependencies WHERE source_artifact_id = ?",
                (source_artifact_id,),
            )
            return [self._row_to_edge(row) for row in cursor.fetchall()]
        finally:
            conn.close()

    def get_by_target(self, target_artifact_id: str) -> List[DependencyEdge]:
        """
        Get all edges pointing to a target artifact.

        Args:
            target_artifact_id: Target artifact ID

        Returns:
            List of edges to this target
        """
        conn = self._get_connection()
        try:
            cursor = conn.execute(
                "SELECT * FROM prompt_dependencies WHERE target_artifact_id = ?",
                (target_artifact_id,),
            )
            return [self._row_to_edge(row) for row in cursor.fetchall()]
        finally:
            conn.close()

    def get_by_run(self, run_id: str) -> List[DependencyEdge]:
        """
        Get all edges created by a specific run.

        Args:
            run_id: Run identifier

        Returns:
            List of edges from this run
        """
        conn = self._get_connection()
        try:
            cursor = conn.execute(
                "SELECT * FROM prompt_dependencies WHERE run_id = ?",
                (run_id,),
            )
            return [self._row_to_edge(row) for row in cursor.fetchall()]
        finally:
            conn.close()

    def get_by_type(self, edge_type: EdgeType) -> List[DependencyEdge]:
        """
        Get all edges of a specific type.

        Args:
            edge_type: Edge type to filter by

        Returns:
            List of edges of this type
        """
        conn = self._get_connection()
        try:
            cursor = conn.execute(
                "SELECT * FROM prompt_dependencies WHERE edge_type = ?",
                (edge_type.value,),
            )
            return [self._row_to_edge(row) for row in cursor.fetchall()]
        finally:
            conn.close()

    def get_all(self) -> List[DependencyEdge]:
        """
        Get all dependency edges.

        Returns:
            List of all edges
        """
        conn = self._get_connection()
        try:
            cursor = conn.execute("SELECT * FROM prompt_dependencies")
            return [self._row_to_edge(row) for row in cursor.fetchall()]
        finally:
            conn.close()

    def delete(self, edge_id: str) -> bool:
        """
        Delete a dependency edge.

        Args:
            edge_id: Edge identifier

        Returns:
            True if deleted, False if not found
        """
        conn = self._get_connection()
        try:
            cursor = conn.execute(
                "DELETE FROM prompt_dependencies WHERE id = ?",
                (edge_id,),
            )
            conn.commit()
            return cursor.rowcount > 0
        finally:
            conn.close()

    def delete_by_run(self, run_id: str) -> int:
        """
        Delete all edges created by a specific run.

        Args:
            run_id: Run identifier

        Returns:
            Number of edges deleted
        """
        conn = self._get_connection()
        try:
            cursor = conn.execute(
                "DELETE FROM prompt_dependencies WHERE run_id = ?",
                (run_id,),
            )
            conn.commit()
            return cursor.rowcount
        finally:
            conn.close()
