import os
import uuid
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

from app.database import get_db
from app.models.document import Document, BorrowRequest, BorrowRequestItem
from app.models.user import User
from app.schemas.document import (
    DocumentCreate, DocumentUpdate, DocumentOut,
    BorrowRequestCreate, BorrowRequestOut,
)
from app.deps import get_current_user
from app.services.notifier import notify_user_by_id, notify_user_group

router = APIRouter(prefix="/documents", tags=["documents"])

BORROW_UPLOAD_DIR = "uploads/borrow"


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

async def _get_doc_or_404(doc_id: int, db: AsyncSession) -> Document:
    doc = await db.get(Document, doc_id)
    if not doc:
        raise HTTPException(404, "Document not found")
    return doc


async def _enrich_doc(doc: Document, db: AsyncSession) -> dict:
    custodian = await db.get(User, doc.custodian_id) if doc.custodian_id else None
    return {
        **doc.__dict__,
        "custodian_name": custodian.username if custodian else None,
    }


async def _enrich_item(item: BorrowRequestItem, db: AsyncSession) -> dict:
    doc = await db.get(Document, item.document_id)
    return {
        **item.__dict__,
        "document_name": doc.name if doc else None,
        "document_location": doc.location if doc else None,
        "document_type": doc.doc_type if doc else None,
        "document_custodian_id": doc.custodian_id if doc else None,
    }


async def _enrich_request(req: BorrowRequest, db: AsyncSession) -> dict:
    requester = await db.get(User, req.requester_id)
    items_res = await db.execute(
        select(BorrowRequestItem).where(BorrowRequestItem.borrow_request_id == req.id)
    )
    items = [await _enrich_item(i, db) for i in items_res.scalars().all()]
    return {
        **req.__dict__,
        "requester_name": requester.username if requester else None,
        "items": items,
    }


async def _save_upload(file: UploadFile) -> str:
    """Save uploaded file to uploads/borrow/, return relative path."""
    ext = os.path.splitext(file.filename or "")[1].lower() or ".jpg"
    filename = f"{uuid.uuid4().hex}{ext}"
    path = os.path.join(BORROW_UPLOAD_DIR, filename)
    with open(path, "wb") as f:
        f.write(await file.read())
    return f"/{path}"


async def _get_doc_names_for_request(req_id: int, db: AsyncSession) -> str:
    """Return comma-separated document names for a borrow request."""
    items_res = await db.execute(
        select(BorrowRequestItem).where(BorrowRequestItem.borrow_request_id == req_id)
    )
    names = []
    for item in items_res.scalars().all():
        doc = await db.get(Document, item.document_id)
        if doc:
            names.append(doc.name)
    return ", ".join(names) if names else "-"


async def _send_photo_to_document_topic(db, file_path: str, filename: str, caption: str) -> None:
    """Read saved photo from disk and send to Document Request Telegram topic."""
    try:
        from app.services.telegram import send_photo
        from app.models.app_setting import AppSetting
        from app.database import AsyncSessionLocal
        async with AsyncSessionLocal() as session:
            chat_setting = await session.get(AppSetting, "telegram_user_group_id")
            thread_setting = await session.get(AppSetting, "telegram_thread_document")
            if not chat_setting or not chat_setting.value:
                return
            thread_id = int(thread_setting.value) if thread_setting and thread_setting.value else None
        # file_path starts with "/" e.g. "/uploads/borrow/..."
        disk_path = file_path.lstrip("/")
        with open(disk_path, "rb") as f:
            file_bytes = f.read()
        await send_photo(chat_setting.value, file_bytes, filename, caption, thread_id)
    except Exception as e:
        print(f"[documents] send_photo_to_telegram error: {e}")


def _get_custodian_id_for_request(items: list[BorrowRequestItem], docs: dict[int, Document]) -> int | None:
    for item in items:
        doc = docs.get(item.document_id)
        if doc and doc.custodian_id:
            return doc.custodian_id
    return None


# ── Document CRUD (admin only for write) ──────────────────────────────────────

@router.get("", response_model=list[DocumentOut])
async def list_documents(
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    result = await db.execute(select(Document).order_by(Document.name))
    return [await _enrich_doc(d, db) for d in result.scalars().all()]


@router.post("", response_model=DocumentOut)
async def create_document(
    payload: DocumentCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if current_user.role != "admin":
        raise HTTPException(403, "Admin only")
    doc = Document(
        name=payload.name,
        doc_type=payload.doc_type,
        location=payload.location,
        custodian_id=payload.custodian_id,
    )
    db.add(doc)
    await db.commit()
    await db.refresh(doc)
    return await _enrich_doc(doc, db)


@router.patch("/{doc_id}", response_model=DocumentOut)
async def update_document(
    doc_id: int,
    payload: DocumentUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if current_user.role != "admin":
        raise HTTPException(403, "Admin only")
    doc = await _get_doc_or_404(doc_id, db)
    if doc.status == "borrowed" and payload.status not in (None, "borrowed", "lost"):
        raise HTTPException(422, "Cannot change status while document is borrowed")
    for field, value in payload.model_dump(exclude_none=True).items():
        setattr(doc, field, value)
    doc.updated_at = datetime.utcnow()
    await db.commit()
    await db.refresh(doc)
    return await _enrich_doc(doc, db)


@router.delete("/{doc_id}", status_code=204)
async def delete_document(
    doc_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if current_user.role != "admin":
        raise HTTPException(403, "Admin only")
    doc = await _get_doc_or_404(doc_id, db)
    if doc.status == "borrowed":
        raise HTTPException(422, "Cannot delete a document that is currently borrowed")
    await db.delete(doc)
    await db.commit()


# ── Borrow Requests ───────────────────────────────────────────────────────────

@router.get("/borrow-requests", response_model=list[BorrowRequestOut])
async def list_borrow_requests(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if current_user.role == "admin":
        result = await db.execute(select(BorrowRequest).order_by(BorrowRequest.created_at.desc()))
        rows = result.scalars().all()
    else:
        # All users see all requests (any Jarvis user can record handover/return)
        result = await db.execute(select(BorrowRequest).order_by(BorrowRequest.created_at.desc()))
        rows = result.scalars().all()
    return [await _enrich_request(r, db) for r in rows]


@router.post("/borrow-requests", response_model=BorrowRequestOut)
async def create_borrow_request(
    payload: BorrowRequestCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if not payload.document_ids:
        raise HTTPException(422, "Select at least one document")

    docs = []
    for doc_id in payload.document_ids:
        doc = await db.get(Document, doc_id)
        if not doc:
            raise HTTPException(404, f"Document {doc_id} not found")
        if doc.status != "available":
            raise HTTPException(422, f"Document '{doc.name}' is not available (status: {doc.status})")
        docs.append(doc)

    custodian_ids = {d.custodian_id for d in docs}
    if len(custodian_ids) > 1:
        raise HTTPException(422, "All documents in one request must have the same custodian. Split into separate requests.")

    req = BorrowRequest(
        requester_id=current_user.id,
        purpose=payload.purpose,
        status="active",
    )
    db.add(req)
    await db.flush()

    items = []
    for doc in docs:
        item = BorrowRequestItem(
            borrow_request_id=req.id,
            document_id=doc.id,
        )
        db.add(item)
        items.append(item)
        doc.status = "borrowed"
        doc.updated_at = datetime.utcnow()

    await db.commit()
    await db.refresh(req)

    custodian_id = list(custodian_ids)[0]
    if custodian_id:
        await notify_user_by_id(
            db, custodian_id,
            f"Pinjam dokumen dari {current_user.username}",
            f"Keperluan: {payload.purpose} ({len(docs)} dokumen)",
            "/documents",
        )
    doc_names = ", ".join(d.name for d in docs)
    await notify_user_group(
        db,
        f"Permintaan pinjam dokumen: {current_user.username}",
        f"Keperluan: {payload.purpose}\nDokumen: {doc_names}",
        "/documents",
        thread_key="telegram_thread_document",
    )
    await db.commit()

    return await _enrich_request(req, db)


@router.get("/borrow-requests/{req_id}", response_model=BorrowRequestOut)
async def get_borrow_request(
    req_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    req = await db.get(BorrowRequest, req_id)
    if not req:
        raise HTTPException(404, "Not found")
    return await _enrich_request(req, db)


@router.post("/borrow-requests/{req_id}/handover", response_model=BorrowRequestOut)
async def record_handover(
    req_id: int,
    handed_by: str = Form(...),
    recipient: str = Form(...),
    handover_date: str = Form(...),
    photo: UploadFile = File(...),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    """Record that documents were handed over to recipient. Any Jarvis user can do this."""
    req = await db.get(BorrowRequest, req_id)
    if not req:
        raise HTTPException(404, "Not found")
    if req.status != "active":
        raise HTTPException(422, f"Cannot record handover: request status is '{req.status}'")

    try:
        parsed_date = datetime.fromisoformat(handover_date)
    except ValueError:
        raise HTTPException(422, "Invalid handover_date format, use ISO 8601")

    photo_path = await _save_upload(photo)

    req.handover_handed_by = handed_by
    req.handover_recipient = recipient
    req.handover_date = parsed_date
    req.handover_photo_path = photo_path
    req.status = "handed_over"

    # Update doc locations to with_borrower
    items_res = await db.execute(
        select(BorrowRequestItem).where(BorrowRequestItem.borrow_request_id == req_id)
    )
    for item in items_res.scalars().all():
        doc = await db.get(Document, item.document_id)
        if doc:
            doc.location = "with_borrower"
            doc.updated_at = datetime.utcnow()

    doc_names = await _get_doc_names_for_request(req.id, db)
    await notify_user_group(
        db,
        f"Serah terima dokumen: #{req.id}",
        f"Diserahkan oleh: {handed_by} → kepada: {recipient}\nDokumen: {doc_names}",
        "/documents",
        thread_key="telegram_thread_document",
    )
    await db.commit()
    import asyncio
    asyncio.create_task(_send_photo_to_document_topic(
        db, photo_path, photo.filename or "handover.jpg",
        f"📸 Foto serah terima #{req.id}\n{handed_by} → {recipient}\nDokumen: {doc_names}",
    ))
    await db.refresh(req)
    return await _enrich_request(req, db)


@router.post("/borrow-requests/{req_id}/return-photo", response_model=BorrowRequestOut)
async def record_return_photo(
    req_id: int,
    handed_by: str = Form(...),
    recipient: str = Form(...),
    return_date: str = Form(...),
    photo: UploadFile = File(...),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    """Record return photo proof. Any Jarvis user can do this."""
    req = await db.get(BorrowRequest, req_id)
    if not req:
        raise HTTPException(404, "Not found")
    if req.status != "handed_over":
        raise HTTPException(422, f"Cannot record return: request status is '{req.status}'")

    try:
        datetime.fromisoformat(return_date)
    except ValueError:
        raise HTTPException(422, "Invalid return_date format, use ISO 8601")

    photo_path = await _save_upload(photo)
    req.return_handed_by = handed_by
    req.return_recipient = recipient
    req.return_photo_path = photo_path
    req.status = "return_pending"

    await db.commit()

    # Notify custodian
    items_res = await db.execute(
        select(BorrowRequestItem).where(BorrowRequestItem.borrow_request_id == req_id)
    )
    items = items_res.scalars().all()
    if items:
        doc = await db.get(Document, items[0].document_id)
        if doc and doc.custodian_id:
            await notify_user_by_id(
                db, doc.custodian_id,
                "Dokumen sedang dikembalikan",
                f"Permintaan #{req_id} menunggu konfirmasi pengembalian dari kamu.",
                "/documents",
            )
    doc_names = await _get_doc_names_for_request(req_id, db)
    await notify_user_group(
        db,
        f"Pengembalian dokumen: #{req_id}",
        f"Dikembalikan oleh: {handed_by} → kepada: {recipient}\nDokumen: {doc_names}",
        "/documents",
        thread_key="telegram_thread_document",
    )
    await db.commit()
    import asyncio
    asyncio.create_task(_send_photo_to_document_topic(
        db, photo_path, photo.filename or "return.jpg",
        f"📸 Foto pengembalian #{req_id}\n{handed_by} → {recipient}\nDokumen: {doc_names}",
    ))

    await db.refresh(req)
    return await _enrich_request(req, db)


@router.post("/borrow-requests/{req_id}/confirm", response_model=BorrowRequestOut)
async def confirm_receipt(
    req_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Custodian confirms documents received. Only custodian or admin."""
    req = await db.get(BorrowRequest, req_id)
    if not req:
        raise HTTPException(404, "Not found")
    if req.status != "return_pending":
        raise HTTPException(422, f"Cannot confirm: request status is '{req.status}'")

    # Check custodian
    items_res = await db.execute(
        select(BorrowRequestItem).where(BorrowRequestItem.borrow_request_id == req_id)
    )
    items = items_res.scalars().all()
    if current_user.role != "admin" and items:
        doc = await db.get(Document, items[0].document_id)
        if doc and doc.custodian_id != current_user.id:
            raise HTTPException(403, "Only the custodian can confirm receipt")

    req.status = "completed"
    req.completed_at = datetime.utcnow()

    for item in items:
        doc = await db.get(Document, item.document_id)
        if doc:
            doc.status = "available"
            doc.location = "home"
            doc.updated_at = datetime.utcnow()

    await db.commit()

    await notify_user_by_id(
        db, req.requester_id,
        "Pengembalian dokumen dikonfirmasi",
        f"Permintaan #{req_id} selesai. Dokumen sudah diterima kembali.",
        "/documents",
    )
    doc_names = await _get_doc_names_for_request(req_id, db)
    await notify_user_group(
        db,
        f"Konfirmasi penerimaan dokumen: #{req_id}",
        f"Diterima kembali oleh {current_user.username}\nDokumen: {doc_names}",
        "/documents",
        thread_key="telegram_thread_document",
    )
    await db.commit()

    await db.refresh(req)
    return await _enrich_request(req, db)


# ── Document-level location actions ──────────────────────────────────────────

@router.patch("/{doc_id}/move-to-bank", response_model=DocumentOut)
async def move_to_bank(
    doc_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Move document from home to bank. Admin only."""
    if current_user.role != "admin":
        raise HTTPException(403, "Admin only")
    doc = await _get_doc_or_404(doc_id, db)
    if doc.location != "home":
        raise HTTPException(422, f"Document is at '{doc.location}', not home")
    doc.location = "bank"
    doc.updated_at = datetime.utcnow()
    await db.commit()
    await notify_user_group(
        db,
        f"Dokumen dipindah ke bank: {doc.name}",
        f"Tipe: {doc.doc_type or '-'} | Oleh: {current_user.username}",
        "/documents",
        thread_key="telegram_thread_document",
    )
    await db.commit()
    await db.refresh(doc)
    return await _enrich_doc(doc, db)


@router.patch("/{doc_id}/retrieve-from-bank", response_model=DocumentOut)
async def retrieve_from_bank(
    doc_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Move document from bank to home. Admin only."""
    if current_user.role != "admin":
        raise HTTPException(403, "Admin only")
    doc = await _get_doc_or_404(doc_id, db)
    if doc.location != "bank":
        raise HTTPException(422, f"Document is at '{doc.location}', not bank")
    doc.location = "home"
    doc.updated_at = datetime.utcnow()
    await db.commit()
    await notify_user_group(
        db,
        f"Dokumen diambil dari bank: {doc.name}",
        f"Tipe: {doc.doc_type or '-'} | Oleh: {current_user.username}",
        "/documents",
        thread_key="telegram_thread_document",
    )
    await db.commit()
    await db.refresh(doc)
    return await _enrich_doc(doc, db)


@router.get("/{doc_id}/history", response_model=list[BorrowRequestOut])
async def get_document_history(
    doc_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    await _get_doc_or_404(doc_id, db)
    result = await db.execute(
        select(BorrowRequest)
        .join(BorrowRequestItem, BorrowRequestItem.borrow_request_id == BorrowRequest.id)
        .where(BorrowRequestItem.document_id == doc_id)
        .order_by(BorrowRequest.created_at.desc())
    )
    return [await _enrich_request(r, db) for r in result.scalars().all()]
