"""Documents router: CRUD for reference documents, items, and item versions."""

from __future__ import annotations

import difflib
import logging
import uuid

from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db
from app.models.document import Document, DocumentItem, ItemVersion
from app.models.user import DuniterIdentity
from app.schemas.document import (
    DocumentCreate,
    DocumentFullOut,
    DocumentItemCreate,
    DocumentItemOut,
    DocumentItemUpdate,
    DocumentOut,
    DocumentUpdate,
    ItemReorderRequest,
    ItemVersionCreate,
    ItemVersionOut,
)
from app.dependencies.org import get_active_org_id
from app.services import document_service
from app.services.auth_service import get_current_identity

logger = logging.getLogger(__name__)

router = APIRouter()


# ── Helpers ─────────────────────────────────────────────────────────────────


async def _get_document_by_slug(db: AsyncSession, slug: str) -> Document:
    """Fetch a document by slug or raise 404."""
    result = await db.execute(select(Document).where(Document.slug == slug))
    doc = result.scalar_one_or_none()
    if doc is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document introuvable")
    return doc


async def _get_item(db: AsyncSession, document_id: uuid.UUID, item_id: uuid.UUID) -> DocumentItem:
    """Fetch a document item by ID within a document, or raise 404."""
    result = await db.execute(
        select(DocumentItem).where(
            DocumentItem.id == item_id,
            DocumentItem.document_id == document_id,
        )
    )
    item = result.scalar_one_or_none()
    if item is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Item introuvable")
    return item


# ── Document routes ─────────────────────────────────────────────────────────


@router.get("/", response_model=list[DocumentOut])
async def list_documents(
    db: AsyncSession = Depends(get_db),
    org_id: uuid.UUID | None = Depends(get_active_org_id),
    doc_type: str | None = Query(default=None, description="Filtrer par type de document"),
    status_filter: str | None = Query(default=None, alias="status", description="Filtrer par statut"),
    skip: int = Query(default=0, ge=0),
    limit: int = Query(default=50, ge=1, le=200),
) -> list[DocumentOut]:
    """List all reference documents, with optional filters."""
    stmt = select(Document)

    if org_id is not None:
        stmt = stmt.where(Document.organization_id == org_id)
    else:
        stmt = stmt.where(Document.organization_id.is_(None))
    if doc_type is not None:
        stmt = stmt.where(Document.doc_type == doc_type)
    if status_filter is not None:
        stmt = stmt.where(Document.status == status_filter)

    stmt = stmt.order_by(Document.created_at.desc()).offset(skip).limit(limit)
    result = await db.execute(stmt)
    documents = result.scalars().all()

    # Compute items_count for each document
    out = []
    for doc in documents:
        count_result = await db.execute(
            select(func.count()).select_from(DocumentItem).where(DocumentItem.document_id == doc.id)
        )
        items_count = count_result.scalar() or 0
        doc_out = DocumentOut.model_validate(doc)
        doc_out.items_count = items_count
        out.append(doc_out)

    return out


@router.post("/", response_model=DocumentOut, status_code=status.HTTP_201_CREATED)
async def create_document(
    payload: DocumentCreate,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
    org_id: uuid.UUID | None = Depends(get_active_org_id),
) -> DocumentOut:
    """Create a new reference document."""
    # Check slug uniqueness
    existing = await db.execute(select(Document).where(Document.slug == payload.slug))
    if existing.scalar_one_or_none() is not None:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Un document avec ce slug existe deja",
        )

    doc = Document(**payload.model_dump(), organization_id=org_id)
    db.add(doc)
    await db.commit()
    await db.refresh(doc)

    doc_out = DocumentOut.model_validate(doc)
    doc_out.items_count = 0
    return doc_out


@router.get("/{slug}", response_model=DocumentOut)
async def get_document(
    slug: str,
    db: AsyncSession = Depends(get_db),
) -> DocumentOut:
    """Get a single document by its slug."""
    doc = await _get_document_by_slug(db, slug)

    count_result = await db.execute(
        select(func.count()).select_from(DocumentItem).where(DocumentItem.document_id == doc.id)
    )
    items_count = count_result.scalar() or 0

    doc_out = DocumentOut.model_validate(doc)
    doc_out.items_count = items_count
    return doc_out


@router.put("/{slug}", response_model=DocumentOut)
async def update_document(
    slug: str,
    payload: DocumentUpdate,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> DocumentOut:
    """Update a document's metadata (title, status, description, version)."""
    doc = await _get_document_by_slug(db, slug)

    update_data = payload.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(doc, field, value)

    await db.commit()
    await db.refresh(doc)

    count_result = await db.execute(
        select(func.count()).select_from(DocumentItem).where(DocumentItem.document_id == doc.id)
    )
    items_count = count_result.scalar() or 0

    doc_out = DocumentOut.model_validate(doc)
    doc_out.items_count = items_count
    return doc_out


# ── Document Item routes ────────────────────────────────────────────────────


@router.post("/{slug}/items", response_model=DocumentItemOut, status_code=status.HTTP_201_CREATED)
async def add_item(
    slug: str,
    payload: DocumentItemCreate,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> DocumentItemOut:
    """Add a new item (clause, rule, etc.) to a document."""
    doc = await _get_document_by_slug(db, slug)

    # Determine sort_order: max existing + 1
    max_order_result = await db.execute(
        select(func.max(DocumentItem.sort_order)).where(DocumentItem.document_id == doc.id)
    )
    max_order = max_order_result.scalar() or 0

    item = DocumentItem(
        document_id=doc.id,
        sort_order=max_order + 1,
        **payload.model_dump(),
    )
    db.add(item)
    await db.commit()
    await db.refresh(item)

    return DocumentItemOut.model_validate(item)


@router.get("/{slug}/items", response_model=list[DocumentItemOut])
async def list_items(
    slug: str,
    db: AsyncSession = Depends(get_db),
) -> list[DocumentItemOut]:
    """List all items in a document, ordered by sort_order."""
    doc = await _get_document_by_slug(db, slug)

    result = await db.execute(
        select(DocumentItem)
        .where(DocumentItem.document_id == doc.id)
        .order_by(DocumentItem.sort_order)
    )
    items = result.scalars().all()
    return [DocumentItemOut.model_validate(item) for item in items]


# NOTE: reorder must be declared BEFORE /{slug}/items/{item_id} routes
# to avoid "reorder" being parsed as a UUID path parameter.


@router.put("/{slug}/items/reorder", response_model=list[DocumentItemOut])
async def reorder_items(
    slug: str,
    payload: ItemReorderRequest,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> list[DocumentItemOut]:
    """Reorder items in a document by updating their sort_order values."""
    doc = await _get_document_by_slug(db, slug)

    items = await document_service.reorder_items(
        doc.id,
        [(entry.item_id, entry.sort_order) for entry in payload.items],
        db,
    )

    return [DocumentItemOut.model_validate(item) for item in items]


@router.get("/{slug}/items/{item_id}", response_model=DocumentItemOut)
async def get_item(
    slug: str,
    item_id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
) -> DocumentItemOut:
    """Get a single item with its version history."""
    doc = await _get_document_by_slug(db, slug)
    item = await _get_item(db, doc.id, item_id)
    return DocumentItemOut.model_validate(item)


@router.post(
    "/{slug}/items/{item_id}/versions",
    response_model=ItemVersionOut,
    status_code=status.HTTP_201_CREATED,
)
async def propose_version(
    slug: str,
    item_id: uuid.UUID,
    payload: ItemVersionCreate,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> ItemVersionOut:
    """Propose a new version of a document item.

    Automatically computes a unified diff between the current text and the proposed text.
    """
    doc = await _get_document_by_slug(db, slug)
    item = await _get_item(db, doc.id, item_id)

    # Compute diff
    diff_lines = difflib.unified_diff(
        item.current_text.splitlines(keepends=True),
        payload.proposed_text.splitlines(keepends=True),
        fromfile="actuel",
        tofile="propose",
    )
    diff_text = "".join(diff_lines) or None

    version = ItemVersion(
        item_id=item.id,
        proposed_text=payload.proposed_text,
        diff_text=diff_text,
        rationale=payload.rationale,
        proposed_by_id=identity.id,
    )
    db.add(version)
    await db.commit()
    await db.refresh(version)

    return ItemVersionOut.model_validate(version)


# ── Item update & delete ───────────────────────────────────────────────────


@router.put("/{slug}/items/{item_id}", response_model=DocumentItemOut)
async def update_item(
    slug: str,
    item_id: uuid.UUID,
    payload: DocumentItemUpdate,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> DocumentItemOut:
    """Update an item's text, title, position, or item_type."""
    doc = await _get_document_by_slug(db, slug)
    item = await _get_item(db, doc.id, item_id)

    update_data = payload.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(item, field, value)

    await db.commit()
    await db.refresh(item)

    return DocumentItemOut.model_validate(item)


@router.delete("/{slug}/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response, response_model=None)
async def delete_item(
    slug: str,
    item_id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> None:
    """Delete a document item.

    Refuses deletion if the item has any active votes (status 'voting').
    """
    doc = await _get_document_by_slug(db, slug)
    item = await _get_item(db, doc.id, item_id)

    # Check for active votes on this item's versions
    active_versions_result = await db.execute(
        select(func.count()).select_from(ItemVersion).where(
            ItemVersion.item_id == item.id,
            ItemVersion.status == "voting",
        )
    )
    active_count = active_versions_result.scalar() or 0
    if active_count > 0:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Impossible de supprimer un element avec des votes en cours",
        )

    await db.delete(item)
    await db.commit()


# ── Version accept & reject ────────────────────────────────────────────────


@router.put(
    "/{slug}/items/{item_id}/versions/{version_id}/accept",
    response_model=DocumentItemOut,
)
async def accept_version(
    slug: str,
    item_id: uuid.UUID,
    version_id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> DocumentItemOut:
    """Accept a proposed version and apply it to the document item.

    Replaces the item's current_text with the version's proposed_text
    and rejects all other pending/voting versions for this item.
    """
    doc = await _get_document_by_slug(db, slug)
    # Verify item belongs to document
    await _get_item(db, doc.id, item_id)

    try:
        updated_item = await document_service.apply_version(item_id, version_id, db)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))

    return DocumentItemOut.model_validate(updated_item)


@router.put(
    "/{slug}/items/{item_id}/versions/{version_id}/reject",
    response_model=ItemVersionOut,
)
async def reject_version(
    slug: str,
    item_id: uuid.UUID,
    version_id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> ItemVersionOut:
    """Reject a proposed version."""
    doc = await _get_document_by_slug(db, slug)
    # Verify item belongs to document
    await _get_item(db, doc.id, item_id)

    try:
        version = await document_service.reject_version(item_id, version_id, db)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))

    return ItemVersionOut.model_validate(version)


# ── Version listing ────────────────────────────────────────────────────────


@router.get(
    "/{slug}/items/{item_id}/versions",
    response_model=list[ItemVersionOut],
)
async def list_versions(
    slug: str,
    item_id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
) -> list[ItemVersionOut]:
    """List all versions for a document item."""
    doc = await _get_document_by_slug(db, slug)
    await _get_item(db, doc.id, item_id)

    result = await db.execute(
        select(ItemVersion)
        .where(ItemVersion.item_id == item_id)
        .order_by(ItemVersion.created_at.desc())
    )
    versions = result.scalars().all()
    return [ItemVersionOut.model_validate(v) for v in versions]


# ── Document full view ─────────────────────────────────────────────────────


@router.get("/{slug}/full", response_model=DocumentFullOut)
async def get_document_full(
    slug: str,
    db: AsyncSession = Depends(get_db),
) -> DocumentFullOut:
    """Get a document with all its items (not just count)."""
    doc = await document_service.get_document_with_items(slug, db)
    if doc is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document introuvable")

    return DocumentFullOut.model_validate(doc)


# ── Document archive to sanctuary ──────────────────────────────────────────


@router.post("/{slug}/archive", status_code=status.HTTP_201_CREATED)
async def archive_document(
    slug: str,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> dict:
    """Archive a document to the sanctuary (IPFS + on-chain hash).

    Serializes the full document text and sends it through the sanctuary pipeline.
    """
    entry = await document_service.archive_document(slug, db)
    return {
        "message": "Document archive avec succes",
        "sanctuary_entry_id": str(entry.id),
        "content_hash": entry.content_hash,
        "ipfs_cid": entry.ipfs_cid,
        "chain_tx_hash": entry.chain_tx_hash,
    }
