"""Phase 11: Playbook service — CRUD + versioning."""

from datetime import datetime
from typing import List, Optional
from uuid import UUID

from sqlalchemy.orm import Session

from app.domain.errors import (
    EntityNotFoundError, DuplicateEntityError,
)
from app.models.knowledge import Playbook, PlaybookVersion
from app.models.technique import Technique


def _get_or_404(db: Session, playbook_id: UUID) -> Playbook:
    pb = db.query(Playbook).filter(
        Playbook.id == playbook_id,
        Playbook.is_active == True,
    ).first()
    if not pb:
        raise EntityNotFoundError("Playbook", str(playbook_id))
    return pb


def _snapshot(db: Session, pb: Playbook, changed_by: Optional[UUID], change_note: Optional[str]):
    """Save a version snapshot of the current playbook state."""
    snap = PlaybookVersion(
        playbook_id=pb.id,
        version=pb.version,
        title=pb.title,
        content=pb.content,
        tools=list(pb.tools or []),
        prerequisites=list(pb.prerequisites or []),
        changed_by=changed_by,
        change_note=change_note,
        created_at=datetime.utcnow(),
    )
    db.add(snap)


# ── Read ──────────────────────────────────────────────────────────────────────

def get_playbook(db: Session, playbook_id: UUID) -> Playbook:
    return _get_or_404(db, playbook_id)


def get_playbook_by_technique_type(
    db: Session, technique_id: UUID, playbook_type: str
) -> Optional[Playbook]:
    return db.query(Playbook).filter(
        Playbook.technique_id == technique_id,
        Playbook.playbook_type == playbook_type,
        Playbook.is_active == True,
    ).first()


def list_playbooks(
    db: Session,
    technique_id: Optional[UUID] = None,
    playbook_type: Optional[str] = None,
    include_inactive: bool = False,
) -> List[Playbook]:
    q = db.query(Playbook)
    if not include_inactive:
        q = q.filter(Playbook.is_active == True)
    if technique_id:
        q = q.filter(Playbook.technique_id == technique_id)
    if playbook_type:
        q = q.filter(Playbook.playbook_type == playbook_type)
    return q.order_by(Playbook.technique_id, Playbook.playbook_type).all()


def get_playbook_versions(db: Session, playbook_id: UUID) -> List[PlaybookVersion]:
    _get_or_404(db, playbook_id)   # 404 guard
    return (
        db.query(PlaybookVersion)
        .filter(PlaybookVersion.playbook_id == playbook_id)
        .order_by(PlaybookVersion.version.desc())
        .all()
    )


# ── Create / Update (upsert) ──────────────────────────────────────────────────

def create_playbook(db: Session, data: dict, user_id: UUID) -> Playbook:
    technique_id  = data["technique_id"]
    playbook_type = data["playbook_type"]

    # Validate technique exists
    tech = db.query(Technique).filter(Technique.id == technique_id).first()
    if not tech:
        raise EntityNotFoundError("Technique", str(technique_id))

    # Check for existing (even soft-deleted) to reactivate
    existing = db.query(Playbook).filter(
        Playbook.technique_id == technique_id,
        Playbook.playbook_type == playbook_type,
    ).first()

    if existing:
        if existing.is_active:
            raise DuplicateEntityError(
                "Playbook",
                "technique_id+playbook_type",
                f"{technique_id}/{playbook_type}",
            )
        # Reactivate soft-deleted playbook
        existing.is_active    = True
        existing.title        = data.get("title", existing.title)
        existing.content      = data.get("content", existing.content)
        existing.tools        = data.get("tools", existing.tools) or []
        existing.prerequisites = data.get("prerequisites", existing.prerequisites) or []
        existing.updated_by   = user_id
        existing.updated_at   = datetime.utcnow()
        existing.version     += 1
        change_note = data.get("change_note")
        _snapshot(db, existing, user_id, change_note or "Reactivated")
        db.commit()
        db.refresh(existing)
        return existing

    pb = Playbook(
        technique_id  = technique_id,
        playbook_type = playbook_type,
        title         = data.get("title", f"{playbook_type.capitalize()} playbook"),
        content       = data.get("content", ""),
        version       = 1,
        tools         = data.get("tools") or [],
        prerequisites = data.get("prerequisites") or [],
        created_by    = user_id,
        updated_by    = user_id,
    )
    db.add(pb)
    db.flush()  # get id
    _snapshot(db, pb, user_id, data.get("change_note") or "Initial version")
    db.commit()
    db.refresh(pb)
    return pb


def update_playbook(db: Session, playbook_id: UUID, data: dict, user_id: UUID) -> Playbook:
    pb = _get_or_404(db, playbook_id)

    # Save snapshot before mutating
    _snapshot(db, pb, user_id, data.get("change_note"))

    if "title" in data and data["title"] is not None:
        pb.title = data["title"]
    if "content" in data and data["content"] is not None:
        pb.content = data["content"]
    if "tools" in data and data["tools"] is not None:
        pb.tools = data["tools"]
    if "prerequisites" in data and data["prerequisites"] is not None:
        pb.prerequisites = data["prerequisites"]

    pb.version    += 1
    pb.updated_by  = user_id
    pb.updated_at  = datetime.utcnow()

    db.commit()
    db.refresh(pb)
    return pb


def restore_version(db: Session, playbook_id: UUID, version_number: int, user_id: UUID) -> Playbook:
    """Roll back a playbook to a specific historical version."""
    pb = _get_or_404(db, playbook_id)

    snap = db.query(PlaybookVersion).filter(
        PlaybookVersion.playbook_id == playbook_id,
        PlaybookVersion.version == version_number,
    ).first()
    if not snap:
        raise EntityNotFoundError("PlaybookVersion", f"{playbook_id}/v{version_number}")

    # Snapshot current state before restoring
    _snapshot(db, pb, user_id, f"Auto-snapshot before restore to v{version_number}")

    pb.title        = snap.title
    pb.content      = snap.content
    pb.tools        = list(snap.tools or [])
    pb.prerequisites = list(snap.prerequisites or [])
    pb.version     += 1
    pb.updated_by   = user_id
    pb.updated_at   = datetime.utcnow()

    _snapshot(db, pb, user_id, f"Restored from v{version_number}")
    db.commit()
    db.refresh(pb)
    return pb


def delete_playbook(db: Session, playbook_id: UUID, user_id: UUID) -> None:
    pb = _get_or_404(db, playbook_id)
    pb.is_active  = False
    pb.updated_by = user_id
    pb.updated_at = datetime.utcnow()
    db.commit()
