"""
Approval Queue for SparkNet.

All sparks go through this queue before being shared to the network.
Nothing leaves your machine without your explicit approval.
"""

import json
import sqlite3
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from typing import List, Optional, Dict, Any

from ..security.scanner import SecurityScanner, ScanResult, ScanFinding, FindingSeverity
from ..models import Spark, SparkType, KnowledgeType


class ApprovalState(Enum):
    """State of a spark in the approval queue."""
    PENDING = "pending"          # Waiting for user review
    APPROVED = "approved"        # User approved, ready to share
    REJECTED = "rejected"        # User rejected, will not share
    BLOCKED = "blocked"          # Auto-blocked due to security scan
    SHARED = "shared"            # Already shared to network
    LOCAL_ONLY = "local_only"    # User chose to keep local only


@dataclass
class ApprovalDecision:
    """Record of an approval decision."""
    spark_id: str
    decision: ApprovalState
    decided_at: str
    decided_by: str = "user"  # "user", "auto", "system"
    reason: Optional[str] = None
    scan_result: Optional[Dict] = None

    def to_dict(self) -> Dict[str, Any]:
        return {
            "spark_id": self.spark_id,
            "decision": self.decision.value,
            "decided_at": self.decided_at,
            "decided_by": self.decided_by,
            "reason": self.reason,
            "scan_result": self.scan_result,
        }


@dataclass
class PendingSpark:
    """A spark waiting in the approval queue."""
    id: str
    content: str
    spark_type: SparkType
    knowledge_type: KnowledgeType
    domain: Optional[str]
    confidence: float
    how_learned: Optional[str]
    tags: List[str]

    # Approval metadata
    state: ApprovalState = ApprovalState.PENDING
    created_at: str = ""
    scan_result: Optional[ScanResult] = None
    warnings: List[str] = field(default_factory=list)

    # Decision tracking
    decision: Optional[ApprovalDecision] = None

    def __post_init__(self):
        if not self.created_at:
            self.created_at = datetime.now(timezone.utc).isoformat()

    @property
    def is_blocked(self) -> bool:
        return self.state == ApprovalState.BLOCKED

    @property
    def has_warnings(self) -> bool:
        return len(self.warnings) > 0 or (
            self.scan_result and not self.scan_result.is_clean
        )

    @property
    def can_approve(self) -> bool:
        """Check if this spark can be approved (not blocked)."""
        return self.state in [ApprovalState.PENDING, ApprovalState.REJECTED]

    def to_dict(self) -> Dict[str, Any]:
        return {
            "id": self.id,
            "content": self.content,
            "spark_type": self.spark_type.value,
            "knowledge_type": self.knowledge_type.value,
            "domain": self.domain,
            "confidence": self.confidence,
            "how_learned": self.how_learned,
            "tags": self.tags,
            "state": self.state.value,
            "created_at": self.created_at,
            "has_warnings": self.has_warnings,
            "warnings": self.warnings,
            "scan_result": self.scan_result.to_dict() if self.scan_result else None,
            "decision": self.decision.to_dict() if self.decision else None,
        }

    @classmethod
    def from_spark(cls, spark: Spark) -> "PendingSpark":
        """Create a PendingSpark from a Spark."""
        return cls(
            id=spark.id,
            content=spark.content,
            spark_type=spark.spark_type,
            knowledge_type=spark.knowledge_type,
            domain=spark.domain,
            confidence=spark.confidence,
            how_learned=spark.how_learned,
            tags=spark.tags,
        )


class ApprovalQueue:
    """
    Manages the approval queue for sparks.

    All sparks must pass through this queue before being shared.
    """

    def __init__(self, db_path: Optional[Path] = None):
        """
        Initialize the approval queue.

        Args:
            db_path: Path to the SQLite database
        """
        if db_path is None:
            db_path = Path.home() / ".sparknet" / "approval_queue.db"
        self.db_path = db_path
        self.db_path.parent.mkdir(parents=True, exist_ok=True)

        self.scanner = SecurityScanner()
        self._init_db()

    def _init_db(self):
        """Initialize the database schema."""
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS pending_sparks (
                    id TEXT PRIMARY KEY,
                    content TEXT NOT NULL,
                    spark_type TEXT NOT NULL,
                    knowledge_type TEXT NOT NULL,
                    domain TEXT,
                    confidence REAL,
                    how_learned TEXT,
                    tags TEXT,
                    state TEXT NOT NULL DEFAULT 'pending',
                    created_at TEXT NOT NULL,
                    scan_result TEXT,
                    warnings TEXT,
                    decision TEXT
                )
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_pending_state
                ON pending_sparks(state)
            """)
            conn.execute("""
                CREATE TABLE IF NOT EXISTS approval_history (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    spark_id TEXT NOT NULL,
                    decision TEXT NOT NULL,
                    decided_at TEXT NOT NULL,
                    decided_by TEXT NOT NULL,
                    reason TEXT,
                    scan_result TEXT
                )
            """)

    def add(self, spark: Spark, auto_scan: bool = True) -> PendingSpark:
        """
        Add a spark to the approval queue.

        Args:
            spark: The spark to add
            auto_scan: Whether to automatically scan for secrets

        Returns:
            PendingSpark with scan results
        """
        pending = PendingSpark.from_spark(spark)

        # Run security scan
        if auto_scan:
            scan_result = self.scanner.scan(spark.content)
            pending.scan_result = scan_result

            # Auto-block if critical issues found
            if scan_result.should_block:
                pending.state = ApprovalState.BLOCKED
                pending.warnings = [
                    f"{f.rule_name}: {f.description}"
                    for f in scan_result.findings
                    if f.severity in [FindingSeverity.CRITICAL, FindingSeverity.HIGH]
                ]
            elif not scan_result.is_clean:
                # Add warnings but don't block
                pending.warnings = [
                    f"{f.rule_name}: {f.description}"
                    for f in scan_result.findings
                ]

        # Store in database
        self._store(pending)

        return pending

    def _store(self, pending: PendingSpark):
        """Store a pending spark in the database."""
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                INSERT OR REPLACE INTO pending_sparks
                (id, content, spark_type, knowledge_type, domain, confidence,
                 how_learned, tags, state, created_at, scan_result, warnings, decision)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """, (
                pending.id,
                pending.content,
                pending.spark_type.value,
                pending.knowledge_type.value,
                pending.domain,
                pending.confidence,
                pending.how_learned,
                json.dumps(pending.tags),
                pending.state.value,
                pending.created_at,
                json.dumps(pending.scan_result.to_dict()) if pending.scan_result else None,
                json.dumps(pending.warnings),
                json.dumps(pending.decision.to_dict()) if pending.decision else None,
            ))

    def approve(
        self,
        spark_id: str,
        reason: Optional[str] = None,
        override_block: bool = False,
    ) -> bool:
        """
        Approve a spark for sharing.

        Args:
            spark_id: ID of the spark to approve
            reason: Optional reason for approval
            override_block: Allow approving blocked sparks (requires explicit consent)

        Returns:
            True if approved, False if not found or cannot approve
        """
        pending = self.get(spark_id)
        if not pending:
            return False

        # Check if can approve
        if pending.state == ApprovalState.BLOCKED and not override_block:
            return False

        # Create decision record
        decision = ApprovalDecision(
            spark_id=spark_id,
            decision=ApprovalState.APPROVED,
            decided_at=datetime.now(timezone.utc).isoformat(),
            decided_by="user",
            reason=reason,
            scan_result=pending.scan_result.to_dict() if pending.scan_result else None,
        )

        pending.state = ApprovalState.APPROVED
        pending.decision = decision

        self._store(pending)
        self._record_history(decision)

        return True

    def reject(self, spark_id: str, reason: Optional[str] = None) -> bool:
        """
        Reject a spark (will not be shared).

        Args:
            spark_id: ID of the spark to reject
            reason: Optional reason for rejection

        Returns:
            True if rejected, False if not found
        """
        pending = self.get(spark_id)
        if not pending:
            return False

        decision = ApprovalDecision(
            spark_id=spark_id,
            decision=ApprovalState.REJECTED,
            decided_at=datetime.now(timezone.utc).isoformat(),
            decided_by="user",
            reason=reason,
        )

        pending.state = ApprovalState.REJECTED
        pending.decision = decision

        self._store(pending)
        self._record_history(decision)

        return True

    def mark_local_only(self, spark_id: str) -> bool:
        """Mark a spark as local-only (will never be shared)."""
        pending = self.get(spark_id)
        if not pending:
            return False

        decision = ApprovalDecision(
            spark_id=spark_id,
            decision=ApprovalState.LOCAL_ONLY,
            decided_at=datetime.now(timezone.utc).isoformat(),
            decided_by="user",
            reason="User chose to keep local only",
        )

        pending.state = ApprovalState.LOCAL_ONLY
        pending.decision = decision

        self._store(pending)
        self._record_history(decision)

        return True

    def mark_shared(self, spark_id: str) -> bool:
        """Mark a spark as shared (after successful upload)."""
        pending = self.get(spark_id)
        if not pending:
            return False

        if pending.state != ApprovalState.APPROVED:
            return False

        pending.state = ApprovalState.SHARED
        self._store(pending)

        return True

    def get(self, spark_id: str) -> Optional[PendingSpark]:
        """Get a pending spark by ID."""
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            row = conn.execute(
                "SELECT * FROM pending_sparks WHERE id = ?",
                (spark_id,)
            ).fetchone()

            if not row:
                return None

            return self._row_to_pending(row)

    def list_pending(self, limit: int = 50) -> List[PendingSpark]:
        """List all pending sparks awaiting review."""
        return self._list_by_state(ApprovalState.PENDING, limit)

    def list_blocked(self, limit: int = 50) -> List[PendingSpark]:
        """List all blocked sparks."""
        return self._list_by_state(ApprovalState.BLOCKED, limit)

    def list_approved(self, limit: int = 50) -> List[PendingSpark]:
        """List all approved sparks ready to share."""
        return self._list_by_state(ApprovalState.APPROVED, limit)

    def _list_by_state(self, state: ApprovalState, limit: int) -> List[PendingSpark]:
        """List sparks by state."""
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            rows = conn.execute(
                "SELECT * FROM pending_sparks WHERE state = ? ORDER BY created_at DESC LIMIT ?",
                (state.value, limit)
            ).fetchall()

            return [self._row_to_pending(row) for row in rows]

    def get_stats(self) -> Dict[str, int]:
        """Get queue statistics."""
        with sqlite3.connect(self.db_path) as conn:
            stats = {}
            for state in ApprovalState:
                count = conn.execute(
                    "SELECT COUNT(*) FROM pending_sparks WHERE state = ?",
                    (state.value,)
                ).fetchone()[0]
                stats[state.value] = count

            stats["total"] = sum(stats.values())
            stats["needs_review"] = stats.get("pending", 0) + stats.get("blocked", 0)

            return stats

    def delete(self, spark_id: str) -> bool:
        """Permanently delete a spark from the queue."""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.execute(
                "DELETE FROM pending_sparks WHERE id = ?",
                (spark_id,)
            )
            return cursor.rowcount > 0

    def update(
        self,
        spark_id: str,
        content: Optional[str] = None,
        domain: Optional[str] = None,
        tags: Optional[List[str]] = None,
        rescan: bool = True,
    ) -> Optional[PendingSpark]:
        """
        Update a spark's content (even after approval).

        Args:
            spark_id: ID of the spark to update
            content: New content (optional)
            domain: New domain (optional)
            tags: New tags (optional)
            rescan: Re-run security scan after update

        Returns:
            Updated PendingSpark or None if not found
        """
        pending = self.get(spark_id)
        if not pending:
            return None

        # Update fields
        if content is not None:
            pending.content = content
        if domain is not None:
            pending.domain = domain
        if tags is not None:
            pending.tags = tags

        # Re-scan if content changed
        if rescan and content is not None:
            scan_result = self.scanner.scan(pending.content)
            pending.scan_result = scan_result
            pending.warnings = [
                f"{f.rule_name}: {f.description}"
                for f in scan_result.findings
            ] if not scan_result.is_clean else []

        self._store(pending)
        return pending

    def revert_to_pending(self, spark_id: str) -> bool:
        """
        Revert a spark back to pending state for re-review.

        Works on approved, rejected, or local_only sparks.
        """
        pending = self.get(spark_id)
        if not pending:
            return False

        if pending.state in [ApprovalState.SHARED, ApprovalState.BLOCKED]:
            return False  # Can't revert shared or blocked

        pending.state = ApprovalState.PENDING
        pending.decision = None
        self._store(pending)
        return True

    def _record_history(self, decision: ApprovalDecision):
        """Record a decision in the history table."""
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                INSERT INTO approval_history
                (spark_id, decision, decided_at, decided_by, reason, scan_result)
                VALUES (?, ?, ?, ?, ?, ?)
            """, (
                decision.spark_id,
                decision.decision.value,
                decision.decided_at,
                decision.decided_by,
                decision.reason,
                json.dumps(decision.scan_result) if decision.scan_result else None,
            ))

    def _row_to_pending(self, row: sqlite3.Row) -> PendingSpark:
        """Convert a database row to a PendingSpark."""
        scan_result = None
        if row["scan_result"]:
            sr_data = json.loads(row["scan_result"])
            # Reconstruct ScanResult (simplified)
            from ..security.scanner import ScanResult, ScanFinding
            from ..security.patterns import FindingType
            findings = []
            for f in sr_data.get("findings", []):
                findings.append(ScanFinding(
                    rule_name=f["rule_name"],
                    finding_type=FindingType(f["finding_type"]) if "finding_type" in f else FindingType.CUSTOM,
                    severity=FindingSeverity(f["severity"]),
                    description=f["description"],
                    matched_text="[STORED]",
                    start_position=f.get("position", {}).get("start", 0),
                    end_position=f.get("position", {}).get("end", 0),
                ))
            scan_result = ScanResult(
                content_hash=sr_data.get("content_hash", ""),
                scanned_at=sr_data.get("scanned_at", ""),
                findings=findings,
            )

        decision = None
        if row["decision"]:
            d_data = json.loads(row["decision"])
            decision = ApprovalDecision(
                spark_id=d_data["spark_id"],
                decision=ApprovalState(d_data["decision"]),
                decided_at=d_data["decided_at"],
                decided_by=d_data.get("decided_by", "user"),
                reason=d_data.get("reason"),
            )

        return PendingSpark(
            id=row["id"],
            content=row["content"],
            spark_type=SparkType(row["spark_type"]),
            knowledge_type=KnowledgeType(row["knowledge_type"]),
            domain=row["domain"],
            confidence=row["confidence"],
            how_learned=row["how_learned"],
            tags=json.loads(row["tags"]) if row["tags"] else [],
            state=ApprovalState(row["state"]),
            created_at=row["created_at"],
            scan_result=scan_result,
            warnings=json.loads(row["warnings"]) if row["warnings"] else [],
            decision=decision,
        )


# Singleton
_queue: Optional[ApprovalQueue] = None


def get_approval_queue() -> ApprovalQueue:
    """Get the default approval queue instance."""
    global _queue
    if _queue is None:
        _queue = ApprovalQueue()
    return _queue
