from datetime import datetime
from typing import Optional

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

from app.database import get_db
from app.models.surat import Surat
from app.models.surat_counter import SuratCounter
from app.models.company import Company
from app.models.vendor import Vendor
from app.models.category import Category
from app.models.user import User
from app.schemas.surat import SuratCreate, SuratUpdate, SuratOut, SuratListOut, SuratVerifyOut
from app.schemas.paged import PagedOut
from app.deps import get_current_user, require_role
from app.services.notifier import notify_admins, notify_user_by_id, notify_user_group, notify_admin_group
from app.services.surat_verify import verify_sig

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


async def _generate_no_surat(
    db: AsyncSession,
    company: Company,
    vendor: Optional[object],
) -> str:
    """Generate letter number: [NNN]/[company]/[vendor]/[MM]/[YYYY]"""
    now = datetime.now()

    result = await db.execute(
        select(SuratCounter).where(SuratCounter.year == now.year).with_for_update()
    )
    counter = result.scalar_one_or_none()

    if counter is None:
        counter = SuratCounter(year=now.year, counter=1)
        db.add(counter)
    else:
        counter.counter += 1

    await db.flush()

    return (
        f"{counter.counter:03d}"
        f"/{company.code.upper()}"
        f"/{vendor.code.upper() if vendor else 'EXT'}"
        f"/{now.strftime('%m')}"
        f"/{now.strftime('%Y')}"
    )


def _rebuild_no_surat_codes(no_surat: str, company: Company, vendor: Optional[object]) -> str:
    """Re-stamp the company/vendor code segments of no_surat after an edit,
    keeping the sequence number and month/year untouched.
    Only touches numbers in the current [NNN]/[company]/[vendor]/[MM]/[YYYY]
    format; legacy/manually-entered numbers are left as-is.
    """
    parts = no_surat.split("/")
    if len(parts) != 5:
        return no_surat
    parts[1] = company.code.upper()
    parts[2] = vendor.code.upper() if vendor else "EXT"
    return "/".join(parts)


async def _get_surat_or_404(db: AsyncSession, surat_id: int) -> Surat:
    result = await db.execute(select(Surat).where(Surat.id == surat_id))
    surat = result.scalar_one_or_none()
    if not surat:
        raise HTTPException(status_code=404, detail="Surat not found")
    return surat


# ── List ───────────────────────────────────────────────────────────────────────

@router.get("", response_model=PagedOut[SuratListOut])
async def list_surat(
    search: Optional[str] = Query(None),
    is_approved: Optional[bool] = Query(None),
    company_id: Optional[int] = Query(None),
    vendor_id: Optional[int] = Query(None),
    category_id: Optional[int] = Query(None),
    task_id: Optional[int] = Query(None),
    page: int = Query(1, ge=1),
    limit: int = Query(20, ge=1, le=500),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    filters = []
    if search:
        filters.append(or_(
            Surat.no_surat.ilike(f"%{search}%"),
            Surat.subject.ilike(f"%{search}%"),
            Surat.perihal.ilike(f"%{search}%"),
        ))
    if is_approved is not None:
        filters.append(Surat.is_approved == is_approved)
    if company_id:
        filters.append(Surat.company_id == company_id)
    if vendor_id:
        filters.append(Surat.vendor_id == vendor_id)
    if category_id:
        filters.append(Surat.category_id == category_id)
    if task_id:
        filters.append(Surat.task_id == task_id)

    count_q = select(func.count()).select_from(Surat)
    if filters:
        count_q = count_q.where(*filters)
    total = await db.scalar(count_q)

    q = select(Surat).order_by(Surat.created_at.desc())
    if filters:
        q = q.where(*filters)
    q = q.offset((page - 1) * limit).limit(limit)
    result = await db.execute(q)
    items = result.scalars().all()

    return PagedOut(items=items, total=total, page=page, limit=limit)


# ── Create ─────────────────────────────────────────────────────────────────────

@router.post("", response_model=SuratOut, status_code=201)
async def create_surat(
    body: SuratCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    company_res = await db.execute(select(Company).where(Company.id == body.company_id))
    company = company_res.scalar_one_or_none()
    if not company:
        raise HTTPException(status_code=400, detail="Company not found")

    vendor = None
    if body.vendor_id:
        vendor_res = await db.execute(select(Vendor).where(Vendor.id == body.vendor_id))
        vendor = vendor_res.scalar_one_or_none()
        if not vendor:
            raise HTTPException(status_code=400, detail="Vendor not found")

    cat_res = await db.execute(select(Category).where(Category.id == body.category_id))
    category = cat_res.scalar_one_or_none()
    if not category:
        raise HTTPException(status_code=400, detail="Category not found")

    no_surat = await _generate_no_surat(db, company, vendor)
    surat = Surat(**body.model_dump(), no_surat=no_surat, created_by=current_user.id)
    db.add(surat)
    try:
        await db.commit()
        await db.refresh(surat)
    except Exception:
        await db.rollback()
        raise

    await notify_admins(db, f"Surat baru butuh approval: {no_surat}", surat.subject or "", f"/letters/{surat.id}")
    await notify_user_group(db, f"Surat baru: {no_surat}", f"{surat.subject or ''} | Dibuat oleh: {current_user.username}", f"/letters/{surat.id}", thread_key="telegram_thread_letter")
    await db.commit()
    # Send PDF to admin group (fire-and-forget)
    import asyncio
    asyncio.create_task(_send_surat_pdf_to_admin(surat, current_user.username))
    return surat


async def _send_surat_pdf_to_admin(surat, creator_username: str) -> None:
    """Generate PDF and send to admin Telegram group with approve/reject instructions."""
    try:
        from app.services.pdf_generator import generate_surat_pdf
        from app.services.telegram import send_document
        from app.models.app_setting import AppSetting
        from app.database import AsyncSessionLocal

        async with AsyncSessionLocal() as db:
            setting = await db.get(AppSetting, "telegram_admin_group_id")
            if not setting or not setting.value:
                return

        pdf_bytes = generate_surat_pdf(
            no_surat=surat.no_surat,
            subject=surat.subject,
            perihal=surat.perihal,
            tanggal=str(surat.tanggal) if surat.tanggal else "-",
            body=surat.body or "",
            tembusan=surat.tembusan or "",
        )

        caption = (
            f"📄 *Surat Baru — Butuh Approval*\n\n"
            f"No: {surat.no_surat}\n"
            f"Subjek: {surat.subject}\n"
            f"Dibuat oleh: {creator_username}\n\n"
            f"Surat ID: #{surat.id}\n\n"
            f"Reply pesan ini dengan:\n"
            f"✅ `approve`\n"
            f"❌ `reject [alasan]`"
        )

        filename = f"surat_{surat.no_surat.replace('/', '-')}.pdf"
        await send_document(setting.value, pdf_bytes, filename, caption)
    except Exception as e:
        print(f"[surat] PDF to Telegram error: {e}")


# ── Public verify (QR code target — no auth) ────────────────────────────────────

@router.get("/verify/{surat_id}", response_model=SuratVerifyOut)
async def verify_surat(
    surat_id: int,
    sig: str,
    db: AsyncSession = Depends(get_db),
):
    surat = await db.get(Surat, surat_id)
    if not surat or not verify_sig(surat.id, surat.no_surat, sig):
        raise HTTPException(status_code=404, detail="Surat tidak ditemukan")

    company = await db.get(Company, surat.company_id)
    vendor = await db.get(Vendor, surat.vendor_id) if surat.vendor_id else None

    return SuratVerifyOut(
        no_surat=surat.no_surat,
        subject=surat.subject,
        perihal=surat.perihal,
        tanggal=surat.tanggal,
        is_approved=surat.is_approved,
        approved_at=surat.approved_at,
        company_name=company.name if company else None,
        vendor_name=vendor.name if vendor else None,
    )


# ── Get ────────────────────────────────────────────────────────────────────────

@router.get("/{surat_id}", response_model=SuratOut)
async def get_surat(
    surat_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    return await _get_surat_or_404(db, surat_id)


# ── Update ─────────────────────────────────────────────────────────────────────

@router.put("/{surat_id}", response_model=SuratOut)
async def update_surat(
    surat_id: int,
    body: SuratUpdate,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    surat = await _get_surat_or_404(db, surat_id)

    # Any edit revokes approval
    surat.is_approved = False
    surat.approved_by = None
    surat.approved_at = None

    update_data = body.model_dump(exclude_none=True)
    for k, v in update_data.items():
        setattr(surat, k, v)

    if "company_id" in update_data or "vendor_id" in update_data:
        company_res = await db.execute(select(Company).where(Company.id == surat.company_id))
        company = company_res.scalar_one_or_none()
        if not company:
            raise HTTPException(status_code=400, detail="Company not found")

        vendor = None
        if surat.vendor_id:
            vendor_res = await db.execute(select(Vendor).where(Vendor.id == surat.vendor_id))
            vendor = vendor_res.scalar_one_or_none()
            if not vendor:
                raise HTTPException(status_code=400, detail="Vendor not found")

        surat.no_surat = _rebuild_no_surat_codes(surat.no_surat, company, vendor)

    try:
        await db.commit()
        await db.refresh(surat)
    except Exception:
        await db.rollback()
        raise
    return surat


# ── Delete ─────────────────────────────────────────────────────────────────────

@router.delete("/{surat_id}", status_code=204)
async def delete_surat(
    surat_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    surat = await _get_surat_or_404(db, surat_id)
    try:
        await db.delete(surat)
        await db.commit()
    except Exception:
        await db.rollback()
        raise


# ── Approve / Unapprove ────────────────────────────────────────────────────────

@router.post("/{surat_id}/approve", response_model=SuratOut)
async def approve_surat(
    surat_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(require_role("admin")),
):
    surat = await _get_surat_or_404(db, surat_id)
    if surat.is_approved:
        raise HTTPException(status_code=400, detail="Already approved")

    surat.is_approved = True
    surat.approved_by = current_user.id
    surat.approved_at = datetime.utcnow()
    try:
        await db.commit()
        await db.refresh(surat)
    except Exception:
        await db.rollback()
        raise

    if surat.created_by and surat.created_by != current_user.id:
        await notify_user_by_id(db, surat.created_by, f"Surat disetujui: {surat.no_surat}", f"Disetujui oleh {current_user.username}", f"/letters/{surat_id}")
    await notify_user_group(db, f"Surat disetujui: {surat.no_surat}", surat.subject or "", f"/letters/{surat_id}", thread_key="telegram_thread_letter")
    await db.commit()
    return surat


@router.delete("/{surat_id}/approve", response_model=SuratOut)
async def unapprove_surat(
    surat_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(require_role("admin")),
):
    surat = await _get_surat_or_404(db, surat_id)
    if not surat.is_approved:
        raise HTTPException(status_code=400, detail="Not approved yet")

    surat.is_approved = False
    surat.approved_by = None
    surat.approved_at = None
    try:
        await db.commit()
        await db.refresh(surat)
    except Exception:
        await db.rollback()
        raise

    if surat.created_by:
        await notify_user_by_id(db, surat.created_by, f"Approval surat dibatalkan: {surat.no_surat}", "Approval telah dicabut oleh admin.", f"/letters/{surat_id}")
    await db.commit()
    return surat
