import json
from typing import Annotated
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from app.auth.deps import CurrentUser, current_user, require_admin
from app.db import get_session
from app.jobs.scheduler import schedule_backfill_for_company
from app.schemas.company import CompanyCreate, CompanyOut, CompanyUpdate, NifLookup
from app.services.nif_lookup import lookup_nif
from app.services.enrichment import schedule_enrichment
from app.services.nif import is_valid_nif

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


_COMPANY_COLS = """
    c.id::text, c.nif, c.legal_name, c.trade_name, c.cae, c.address, c.status,
    c.monitored, c.monitoring_type, c.data_coverage_start, c.data_coverage_end,
    c.risk_score, c.ptdata_fetched_at, c.created_at, c.updated_at, c.active,
    (SELECT ac.alvara_number FROM alvara_companies ac
     WHERE ac.nipc = c.nif AND ac.alvara_type = 'A' AND ac.removed_at IS NULL
     LIMIT 1) AS alvara_number,
    (SELECT MIN(dp.date) FROM dre_publications dp
     WHERE dp.company_id = c.id
       AND dp.source = 'mj'
       AND (dp.title ILIKE '%constitui%' OR dp.change_kind = 'constitution')
    ) AS founded_date
"""


def _row_to_company(r) -> CompanyOut:
    return CompanyOut(
        id=r[0], nif=r[1], legal_name=r[2], trade_name=r[3], cae=r[4], address=r[5],
        status=r[6], monitored=r[7], monitoring_type=r[8],
        data_coverage_start=r[9], data_coverage_end=r[10], risk_score=r[11],
        ptdata_fetched_at=r[12], created_at=r[13], updated_at=r[14],
        active=r[15] if len(r) > 15 else True,
        alvara_number=r[16] if len(r) > 16 else None,
        founded_date=r[17] if len(r) > 17 else None,
    )


@router.get("", response_model=list[CompanyOut])
async def list_companies(
    session: Annotated[AsyncSession, Depends(get_session)],
    _user: Annotated[CurrentUser, Depends(current_user)],
    q: str | None = Query(default=None, max_length=200),
    monitoring_type: str | None = Query(
        default=None, pattern="^(internal|competitor|analysis|related|client)$"
    ),
    active: bool | None = None,
) -> list[CompanyOut]:
    where: list[str] = []
    params: dict[str, object] = {}
    if q:
        where.append("(c.legal_name ILIKE :q OR c.nif ILIKE :q)")
        params["q"] = f"%{q}%"
    if monitoring_type:
        where.append("c.monitoring_type = :mt")
        params["mt"] = monitoring_type
    if active is not None:
        where.append("c.active = :act")
        params["act"] = active
    where_sql = ("WHERE " + " AND ".join(where)) if where else ""
    rows = (
        await session.execute(
            text(f"SELECT {_COMPANY_COLS} FROM companies c {where_sql} ORDER BY c.legal_name"),
            params,
        )
    ).all()
    return [_row_to_company(r) for r in rows]


@router.get("/mj-state")
async def companies_mj_state(
    session: Annotated[AsyncSession, Depends(get_session)],
    _user: Annotated[CurrentUser, Depends(current_user)],
    monitoring_type: str | None = Query(
        default=None, pattern="^(internal|competitor|analysis|related|client)$"
    ),
) -> dict[str, dict]:
    """Per-company MJ publication capture state. Used by the companies
    list + detail header to show "last updated" so the user knows which
    firms are due for a re-capture and which were just refreshed.
    Returned as a map keyed by company_id for cheap client-side merge."""
    where_co = ""
    params: dict[str, object] = {}
    if monitoring_type:
        where_co = "AND c.monitoring_type = :mt"
        params["mt"] = monitoring_type
    rows = (
        await session.execute(
            text(
                f"""
                SELECT c.id::text,
                       MAX(d.created_at) AS last_captured_at,
                       MAX(d.date)       AS last_pub_date,
                       COUNT(d.id)       AS total,
                       COUNT(d.id) FILTER (
                         WHERE COALESCE(length(d.raw_json->>'detail_body'), 0) > 0
                       ) AS with_detail
                FROM companies c
                LEFT JOIN dre_publications d
                       ON d.company_id = c.id AND d.source = 'mj'
                WHERE c.active = true {where_co}
                GROUP BY c.id
                """
            ),
            params,
        )
    ).all()
    return {
        r[0]: {
            "last_captured_at": r[1].isoformat() if r[1] else None,
            "last_pub_date": r[2].isoformat() if r[2] else None,
            "total": int(r[3] or 0),
            "with_detail": int(r[4] or 0),
        }
        for r in rows
    }


@router.get("/lookup-by-name")
async def company_lookup_by_name(
    _admin: Annotated[CurrentUser, Depends(require_admin)],
    q: str = Query(min_length=3, max_length=200),
) -> list[dict[str, object]]:
    """Pesquisa inversa nome -> NIPC, via SICAE.

    Útil quando um anúncio de insolvência ou uma publicação traz uma entidade
    sem NIF: o nome é o único ponto de partida para a identificar.
    """
    from app.services.nif_lookup import lookup_by_name

    return await lookup_by_name(q)


@router.get("/lookup/{nif}", response_model=NifLookup)
async def company_lookup(
    nif: str,
    _admin: Annotated[CurrentUser, Depends(require_admin)],
) -> NifLookup:
    if not is_valid_nif(nif):
        return NifLookup(nif=nif, valid=False, source="local")
    data = await lookup_nif(nif)
    return NifLookup(**data)


@router.post("", response_model=CompanyOut, status_code=status.HTTP_201_CREATED)
async def create_company(
    payload: CompanyCreate,
    session: Annotated[AsyncSession, Depends(get_session)],
    _admin: Annotated[CurrentUser, Depends(require_admin)],
) -> CompanyOut:
    if not is_valid_nif(payload.nif):
        raise HTTPException(status.HTTP_400_BAD_REQUEST, "invalid NIF checksum")
    existing = (
        await session.execute(text("SELECT 1 FROM companies WHERE nif = :n"), {"n": payload.nif})
    ).first()
    if existing:
        raise HTTPException(status.HTTP_409_CONFLICT, "NIF already exists")

    # Skip the slow VIES+SICAE roundtrip if the client already supplied a name
    # (typical flow: user clicks "Procurar" first, fields autofill, then "Gravar").
    # Background `refresh_nif` job will fill any gaps later.
    if payload.legal_name:
        legal = payload.legal_name
        trade = payload.trade_name
        cae = payload.cae
        addr = payload.address
        st = payload.status
        ptdata_payload = None
        ptdata_fetched_at_sql = "NULL"
    else:
        enrich = await lookup_nif(payload.nif)
        legal = enrich.get("legal_name") or ""
        trade = enrich.get("trade_name")
        cae = enrich.get("cae")
        addr = enrich.get("address")
        st = enrich.get("status")
        ptdata_payload = enrich.get("raw")
        # Qualquer fonte externa conta: com VIES+SICAE a string nunca contem
        # "ptdata", e testar por isso deixava toda a empresa nova como stale.
        ptdata_fetched_at_sql = "now()" if enrich.get("source") != "local" else "NULL"

    row = (
        await session.execute(
            text(
                f"""
                INSERT INTO companies (nif, legal_name, trade_name, cae, address, status,
                                       ptdata_payload, ptdata_fetched_at, monitored,
                                       monitoring_type)
                VALUES (:nif, :legal, :trade, :cae, :addr, :status,
                        CAST(:pt AS JSONB), {ptdata_fetched_at_sql}, :monitored,
                        :mt)
                RETURNING {_COMPANY_COLS}
                """
            ),
            {
                "nif": payload.nif,
                "legal": legal,
                "trade": trade,
                "cae": cae,
                "addr": addr,
                "status": st,
                "pt": json.dumps(ptdata_payload) if ptdata_payload else None,
                "monitored": payload.monitored,
                "mt": payload.monitoring_type,
            },
        )
    ).first()
    await session.commit()

    # Fire-and-forget ptdata enrichment in the background to fill CAE later.
    if not cae:
        schedule_enrichment(row[0], payload.nif)

    # Kick off first-run backfill: distribuicao (~180d max, server limit) +
    # cire (all) + contracts (ptdata) + MJ publications (Playwright). Runs in
    # the background so the POST returns immediately; progress is visible in
    # /admin/scraping. Skipped if user created the row with monitored=False.
    if payload.monitored:
        schedule_backfill_for_company(row[0], payload.nif)

    return _row_to_company(row)


@router.get("/{company_id}", response_model=CompanyOut)
async def get_company(
    company_id: UUID,
    session: Annotated[AsyncSession, Depends(get_session)],
    _user: Annotated[CurrentUser, Depends(current_user)],
) -> CompanyOut:
    row = (
        await session.execute(
            text(f"SELECT {_COMPANY_COLS} FROM companies c WHERE c.id = :id"),
            {"id": str(company_id)},
        )
    ).first()
    if not row:
        raise HTTPException(status.HTTP_404_NOT_FOUND)
    return _row_to_company(row)


@router.patch("/{company_id}", response_model=CompanyOut)
async def update_company(
    company_id: UUID,
    payload: CompanyUpdate,
    session: Annotated[AsyncSession, Depends(get_session)],
    _admin: Annotated[CurrentUser, Depends(require_admin)],
) -> CompanyOut:
    fields = payload.model_dump(exclude_unset=True)
    if not fields:
        raise HTTPException(status.HTTP_400_BAD_REQUEST, "nothing to update")
    sets = ", ".join(f"{k} = :{k}" for k in fields.keys())
    params = {**fields, "id": str(company_id)}
    row = (
        await session.execute(
            text(
                f"""
                UPDATE companies SET {sets}, updated_at = now() WHERE id = :id
                RETURNING {_COMPANY_COLS}
                """
            ),
            params,
        )
    ).first()
    if not row:
        raise HTTPException(status.HTTP_404_NOT_FOUND)
    await session.commit()
    return _row_to_company(row)


@router.delete("/{company_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_company(
    company_id: UUID,
    session: Annotated[AsyncSession, Depends(get_session)],
    _admin: Annotated[CurrentUser, Depends(require_admin)],
) -> None:
    await session.execute(text("DELETE FROM companies WHERE id = :id"), {"id": str(company_id)})
    await session.commit()
