from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession


async def compute_risk(session: AsyncSession, company_id: str) -> tuple[int, list[str]]:
    """Return (risk_score 0..100, list of human-readable reasons).

    Heuristic rules (stack and cap at 100):
      +50  insolvency detected (species matches 'insolvência' or source='cire' with role 'Devedor'/'Insolvente')
      +20  executions present (species/type matches 'execu'/'executado'/'executiv')
      +10  increasing 30d trend vs previous 30d (count went up)
      +5   high volume (total > 20)
    """
    score = 0
    reasons: list[str] = []

    metrics = (
        await session.execute(
            text(
                """
                SELECT
                  count(*) AS total,
                  count(*) FILTER (WHERE date_filed >= (now() - interval '30 days')::date) AS d30,
                  count(*) FILTER (
                    WHERE date_filed >= (now() - interval '60 days')::date
                      AND date_filed <  (now() - interval '30 days')::date
                  ) AS d30_prev,
                  count(*) FILTER (
                    WHERE (species ILIKE '%insolv%')
                       OR (source = 'cire' AND role_in_process ILIKE ANY (ARRAY['devedor','insolvente']))
                  ) AS insolvencies,
                  count(*) FILTER (
                    WHERE species ILIKE '%execu%'
                       OR role_in_process ILIKE 'executado'
                  ) AS executions
                FROM processes WHERE company_id = :cid
                """
            ),
            {"cid": company_id},
        )
    ).first()
    total, d30, d30_prev, insolvencies, executions = metrics

    if insolvencies:
        score += 50
        reasons.append(f"insolvência detetada ({insolvencies} publicações)")
    if executions:
        score += 20
        reasons.append(f"execuções presentes ({executions})")
    if d30 and d30_prev and d30 > d30_prev:
        score += 10
        reasons.append(f"tendência crescente ({d30_prev} → {d30} em 30 dias)")
    if total > 20:
        score += 5
        reasons.append(f"volume elevado ({total} processos)")

    score = min(100, score)
    return score, reasons


async def update_company_risk(session: AsyncSession, company_id: str) -> int:
    score, _ = await compute_risk(session, company_id)
    await session.execute(
        text("UPDATE companies SET risk_score = :s WHERE id = :id"),
        {"s": score, "id": company_id},
    )
    return score


async def refresh_all_risk(
    session: AsyncSession, company_ids: list[str] | None = None
) -> int:
    """Recalcula o risco em lote. **Não** faz commit.

    O risco só era escrito quando entrava um processo novo, o que tem duas
    consequências: uma empresa cujos processos deixem de contar para as regras
    (a tendência de 30 dias muda todos os dias) fica com o valor da última vez
    que apanhou alguma coisa, e uma empresa que nunca teve processos nunca teve
    valor nenhum.

    Sem processos escreve-se **nulo** e não zero. Zero lê-se como "avaliámos e o
    risco é baixo"; a verdade é que não há nada em que basear uma avaliação, e é
    a ficha que decide como dizer isso — "sem incidentes" quando já passámos por
    lá, "sem dados" quando nunca passámos.

    Usa o `compute_risk` em vez de repetir a heurística em SQL: duas cópias da
    mesma regra divergem, e esta muda.
    """
    rows = (
        await session.execute(
            text(
                """
                SELECT c.id::text,
                       (SELECT count(*) FROM processes p WHERE p.company_id = c.id) AS n
                  FROM companies c
                 WHERE c.active
                   AND (:todos OR c.id = ANY(CAST(:ids AS UUID[])))
                """
            ),
            {"todos": company_ids is None, "ids": company_ids or []},
        )
    ).all()

    changed = 0
    for company_id, n in rows:
        score = await compute_risk(session, company_id) if n else None
        value = score[0] if score else None
        result = await session.execute(
            text(
                "UPDATE companies SET risk_score = :s"
                " WHERE id = CAST(:id AS UUID) AND risk_score IS DISTINCT FROM :s"
            ),
            {"s": value, "id": company_id},
        )
        changed += result.rowcount or 0
    return changed
