import io
import json
import os
import re
import tempfile
import time
import uuid
import aiofiles
import pypdf
from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_, or_
from datetime import date as _date
from typing import Optional
from google import genai
from google.genai import types as genai_types

from app.database import get_db
from app.models.contract import Contract
from app.models.contract_link import ContractLink
from app.models.user import User
from app.models.company import Company
from app.models.vendor import Vendor
from app.schemas.contract import ContractCreate, ContractUpdate, ContractOut
from app.schemas.paged import PagedOut
from app.deps import get_current_user, require_role
from app.services.notifier import notify_admins, notify_admin_group, notify_user_group


async def _enrich(contract: Contract, db: AsyncSession) -> dict:
    company_name = None
    vendor_name = None
    if contract.company_id:
        r = await db.execute(select(Company).where(Company.id == contract.company_id))
        c = r.scalar_one_or_none()
        company_name = c.name if c else None
    if contract.vendor_id:
        r = await db.execute(select(Vendor).where(Vendor.id == contract.vendor_id))
        v = r.scalar_one_or_none()
        vendor_name = v.name if v else None
    # predecessors (contracts this one replaces)
    r = await db.execute(
        select(Contract.id, Contract.title)
        .join(ContractLink, ContractLink.predecessor_id == Contract.id)
        .where(ContractLink.successor_id == contract.id)
    )
    predecessors = [{"id": row.id, "title": row.title} for row in r.all()]
    # successors (contracts that replace this one)
    r = await db.execute(
        select(Contract.id, Contract.title)
        .join(ContractLink, ContractLink.successor_id == Contract.id)
        .where(ContractLink.predecessor_id == contract.id)
    )
    successors = [{"id": row.id, "title": row.title} for row in r.all()]
    return {
        **contract.__dict__,
        "company_name": company_name,
        "vendor_name": vendor_name,
        "predecessors": predecessors,
        "successors": successors,
    }


async def _sync_predecessors(contract_id: int, predecessor_ids: list[int], db: AsyncSession) -> None:
    """Replace all predecessor links for a contract."""
    await db.execute(
        __import__('sqlalchemy').delete(ContractLink).where(ContractLink.successor_id == contract_id)
    )
    for pid in predecessor_ids:
        if pid != contract_id:
            db.add(ContractLink(predecessor_id=pid, successor_id=contract_id))

def parse_gemini_json(raw: str) -> dict:
    """Parse JSON from Gemini response, with regex fallback for edge cases."""
    try:
        return json.loads(raw)
    except json.JSONDecodeError:
        # Fallback: extract the outermost {...} block (e.g. when Gemini wraps in markdown)
        print(f"[analyze-pdf] json.loads failed, attempting regex fallback. raw[:200]={raw[:200]!r}")
        m = re.search(r'\{.*\}', raw, re.DOTALL)
        if m:
            return json.loads(m.group())
        raise


router = APIRouter(prefix="/contracts", tags=["contracts"])
UPLOAD_DIR = os.path.join(os.path.dirname(__file__), "../../uploads")


@router.get("", response_model=PagedOut[ContractOut])
async def list_contracts(
    search: Optional[str] = Query(None),
    company_id: Optional[int] = Query(None),
    vendor_id: Optional[int] = Query(None),
    status: Optional[str] = Query(None),   # "active" | "expired" | "upcoming"
    page: int = Query(1, ge=1),
    limit: int = Query(20, ge=1, le=500),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    today = _date.today()
    filters = []
    if search:
        filters.append(Contract.title.ilike(f"%{search}%"))
    if company_id:
        filters.append(Contract.company_id == company_id)
    if vendor_id:
        filters.append(Contract.vendor_id == vendor_id)
    if status == "active":
        filters.append(and_(Contract.period_end >= today, Contract.period_start <= today))
    elif status == "expired":
        filters.append(Contract.period_end < today)
    elif status == "upcoming":
        filters.append(Contract.period_start > today)

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

    q = select(Contract).order_by(Contract.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()
    enriched = [await _enrich(c, db) for c in items]

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


@router.post("/analyze-pdf")
async def analyze_pdf(
    file: UploadFile = File(...),
    _: User = Depends(get_current_user),
):
    """Upload a PDF contract and extract summary + dates using Gemini AI.
    Supports both text-based and scanned (image) PDFs."""
    from app.config import settings

    if not settings.GEMINI_API_KEY:
        raise HTTPException(503, "AI analysis not configured")

    content = await file.read()
    if not content:
        raise HTTPException(400, "Empty file uploaded")

    PROMPT = """Kamu adalah Legal Counsel berpengalaman 25+ tahun yang ahli dalam hukum kontrak komersial Indonesia. Tugasmu adalah menganalisis kontrak ini secara mendalam dan profesional dalam BAHASA INDONESIA.

Berikan analisis lengkap dengan format HTML berikut:

<h4>📋 Ringkasan Kontrak</h4>
<p>Jelaskan secara ringkas: nama kontrak, para pihak yang terlibat (siapa pemberi kerja, siapa pelaksana), ruang lingkup pekerjaan/jasa, dan esensi utama kesepakatan. Sertakan nomor kontrak jika ada.</p>

<h4>💰 Harga & Nilai Kontrak</h4>
[Jika ada tabel harga/tarif, buat tabel HTML lengkap. Jika nilai kontrak tunggal, jelaskan dalam paragraf. Sertakan mata uang, PPN, dan syarat pembayaran.]
<table class="table table-bordered table-sm"><thead><tr><th>Item</th><th>Satuan</th><th>Harga</th></tr></thead><tbody><tr><td>...</td><td>...</td><td>...</td></tr></tbody></table>

<h4>📅 Jangka Waktu</h4>
<p>Tanggal mulai, tanggal berakhir, total durasi. Sebutkan apakah ada klausul perpanjangan otomatis atau opsi renewal, beserta syaratnya.</p>

<h4>⚠️ Sanksi & Denda</h4>
<ul>
<li>Setiap klausul sanksi, denda keterlambatan, penalti kinerja, atau konsekuensi wanprestasi — kutip pasal yang relevan</li>
</ul>

<h4>🔴 Pasal-Pasal Kritis & Risiko Hukum</h4>
<p><strong>Sebagai legal counsel berpengalaman, berikut pasal-pasal yang perlu diwaspadai karena berpotensi merugikan:</strong></p>
<ul>
<li><strong>Pasal [X] — [Judul risiko]:</strong> Penjelasan mengapa pasal ini berisiko dan rekomendasi negosiasi atau mitigasi</li>
</ul>
<p><em>Catatan: Identifikasi minimal 3-5 risiko konkret seperti: klausul sepihak, force majeure yang terlalu luas, pembatasan tanggung jawab yang tidak adil, ketentuan pemutusan yang merugikan, hak audit, kerahasiaan, atau ketentuan hukum yang berlaku.</em></p>

Kembalikan HANYA JSON valid, tanpa markdown, tanpa code block:
{"title": "Judul singkat kontrak (maks 100 karakter)", "summary": "<h4>...</h4>...", "period_start": "YYYY-MM-DD atau null", "period_end": "YYYY-MM-DD atau null"}"""

    client = genai.Client(api_key=settings.GEMINI_API_KEY)

    try:
        # Try text extraction first (fast, no upload needed)
        reader = pypdf.PdfReader(io.BytesIO(content))
        text = "\n".join(page.extract_text() or "" for page in reader.pages).strip()
    except Exception:
        text = ""

    # Force Gemini to return valid JSON via response_mime_type
    # max_output_tokens=8192 prevents truncation of long HTML summaries (which breaks JSON)
    json_config = genai_types.GenerateContentConfig(
        response_mime_type="application/json",
        max_output_tokens=8192,
    )

    try:
        if text:
            # Text-based PDF — fast path, no file upload needed
            response = client.models.generate_content(
                model="gemini-2.5-flash",
                contents=f"{PROMPT}\n\nContract text:\n{text[:20000]}",
                config=json_config,
            )
        else:
            # Scanned/image PDF — upload to Gemini Files API
            # Write to temp file
            with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
                tmp.write(content)
                tmp_path = tmp.name

            try:
                uploaded = client.files.upload(
                    file=tmp_path,
                    config={"mime_type": "application/pdf"},
                )
            finally:
                os.unlink(tmp_path)

            # Wait until file is ready (ACTIVE state)
            for _ in range(30):
                f = client.files.get(name=uploaded.name)
                if f.state.name == "ACTIVE":
                    break
                if f.state.name == "FAILED":
                    raise HTTPException(500, "Gemini file processing failed")
                time.sleep(2)

            response = client.models.generate_content(
                model="gemini-2.5-flash",
                contents=[uploaded, PROMPT],
                config=json_config,
            )

        result = parse_gemini_json(response.text)

        def clean_date(v):
            return v if v and v not in ("null", "") else None
        return {
            "title":        result.get("title", ""),
            "summary":      result.get("summary", ""),
            "period_start": clean_date(result.get("period_start")),
            "period_end":   clean_date(result.get("period_end")),
        }
    except HTTPException:
        raise
    except Exception as e:
        print(f"[analyze-pdf] exception: {type(e).__name__}: {e}")
        raise HTTPException(500, f"AI analysis failed: {str(e)}")


@router.post("", response_model=ContractOut, status_code=201)
async def create_contract(
    body: ContractCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    data = body.model_dump()
    predecessor_ids = data.pop('predecessor_ids', [])
    contract = Contract(**data, created_by=current_user.id)
    db.add(contract)
    await db.flush()
    await _sync_predecessors(contract.id, predecessor_ids, db)
    await db.commit()
    await db.refresh(contract)

    if current_user.role != "admin":
        await notify_admins(db, f"Kontrak baru: {contract.title}", f"Ditambahkan oleh {current_user.username}", f"/contracts/{contract.id}")
        await db.commit()
    await notify_user_group(db, f"Kontrak baru: {contract.title}", f"Ditambahkan oleh {current_user.username}", f"/contracts/{contract.id}", thread_key="telegram_thread_contract")
    await db.commit()
    return await _enrich(contract, db)


@router.get("/{contract_id}", response_model=ContractOut)
async def get_contract(
    contract_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    result = await db.execute(select(Contract).where(Contract.id == contract_id))
    contract = result.scalar_one_or_none()
    if not contract:
        raise HTTPException(status_code=404, detail="Contract not found")
    return await _enrich(contract, db)


@router.put("/{contract_id}", response_model=ContractOut)
async def update_contract(
    contract_id: int,
    body: ContractUpdate,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    result = await db.execute(select(Contract).where(Contract.id == contract_id))
    contract = result.scalar_one_or_none()
    if not contract:
        raise HTTPException(status_code=404, detail="Contract not found")
    data = body.model_dump(exclude_unset=True)
    predecessor_ids = data.pop('predecessor_ids', None)
    for k, v in data.items():
        setattr(contract, k, v)
    if predecessor_ids is not None:
        await _sync_predecessors(contract_id, predecessor_ids, db)
    await db.commit()
    await db.refresh(contract)
    return await _enrich(contract, db)


@router.post("/{contract_id}/predecessors", response_model=ContractOut)
async def add_predecessor(
    contract_id: int,
    body: dict,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    """Add a single predecessor link (from old contract's 'Tandai Pengganti')."""
    predecessor_id = body.get("predecessor_id")
    if not predecessor_id or predecessor_id == contract_id:
        raise HTTPException(400, "Invalid predecessor_id")
    # Upsert — ignore if already exists
    existing = await db.execute(
        select(ContractLink).where(
            ContractLink.predecessor_id == predecessor_id,
            ContractLink.successor_id == contract_id,
        )
    )
    if not existing.scalar_one_or_none():
        db.add(ContractLink(predecessor_id=predecessor_id, successor_id=contract_id))
        await db.commit()
    result = await db.execute(select(Contract).where(Contract.id == contract_id))
    contract = result.scalar_one_or_none()
    if not contract:
        raise HTTPException(404, "Contract not found")
    return await _enrich(contract, db)


@router.delete("/{contract_id}/predecessors/{predecessor_id}", response_model=ContractOut)
async def remove_predecessor(
    contract_id: int,
    predecessor_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    """Remove a single predecessor link."""
    await db.execute(
        __import__('sqlalchemy').delete(ContractLink).where(
            ContractLink.predecessor_id == predecessor_id,
            ContractLink.successor_id == contract_id,
        )
    )
    await db.commit()
    result = await db.execute(select(Contract).where(Contract.id == contract_id))
    contract = result.scalar_one_or_none()
    if not contract:
        raise HTTPException(404, "Contract not found")
    return await _enrich(contract, db)


@router.delete("/{contract_id}", status_code=204)
async def delete_contract(
    contract_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(require_role("admin")),
):
    result = await db.execute(select(Contract).where(Contract.id == contract_id))
    contract = result.scalar_one_or_none()
    if not contract:
        raise HTTPException(status_code=404, detail="Contract not found")
    await db.delete(contract)
    await db.commit()


@router.post("/{contract_id}/file", response_model=ContractOut)
async def upload_contract_file(
    contract_id: int,
    file: UploadFile = File(...),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    result = await db.execute(select(Contract).where(Contract.id == contract_id))
    contract = result.scalar_one_or_none()
    if not contract:
        raise HTTPException(status_code=404, detail="Contract not found")

    ext = os.path.splitext(file.filename or "")[1]
    fname = f"{uuid.uuid4().hex}{ext}"
    fpath = os.path.join(UPLOAD_DIR, "contracts", fname)
    os.makedirs(os.path.dirname(fpath), exist_ok=True)
    async with aiofiles.open(fpath, "wb") as f:
        await f.write(await file.read())

    contract.file_path = f"/uploads/contracts/{fname}"
    await db.commit()
    await db.refresh(contract)
    return contract


@router.post("/{contract_id}/bukti", response_model=ContractOut)
async def upload_bukti_transfer(
    contract_id: int,
    file: UploadFile = File(...),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    result = await db.execute(select(Contract).where(Contract.id == contract_id))
    contract = result.scalar_one_or_none()
    if not contract:
        raise HTTPException(status_code=404, detail="Contract not found")

    ext = os.path.splitext(file.filename or "")[1]
    fname = f"{uuid.uuid4().hex}{ext}"
    fpath = os.path.join(UPLOAD_DIR, "bukti", fname)
    os.makedirs(os.path.dirname(fpath), exist_ok=True)
    async with aiofiles.open(fpath, "wb") as f:
        await f.write(await file.read())

    contract.bukti_transfer_path = f"/uploads/bukti/{fname}"
    await db.commit()
    await db.refresh(contract)
    return contract
