"""On-demand company analysis.

Given a NIF (+ optional name), ensure a `companies` row exists (creating one
with monitoring_type='analysis' if missing), run scrapers synchronously for
that single company, then compute metrics + risk score.
"""
import json
import logging
from typing import Any

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

from app.db import AsyncSessionLocal
from app.scrapers.cire import scrape_cire_for_nif
from app.scrapers.distribuicao import (
    discover_tribunal_codes,
    scrape_distribuicao_range,
    short_search_term,
)
from app.services.nif_lookup import lookup_nif
from app.services.ingest import upsert_process
from app.services.nif import is_valid_nif
from app.services.risk import compute_risk, update_company_risk

logger = logging.getLogger(__name__)


async def _ensure_company(
    session: AsyncSession, nif: str, name_hint: str | None
) -> tuple[dict[str, Any], bool]:
    """Return ({id, nif, legal_name}, created_flag)."""
    row = (
        await session.execute(
            text("SELECT id::text, nif, legal_name, monitoring_type FROM companies WHERE nif = :n"),
            {"n": nif},
        )
    ).first()
    if row:
        return (
            {"id": row[0], "nif": row[1], "legal_name": row[2], "monitoring_type": row[3]},
            False,
        )
    # Create with monitoring_type='analysis'. Auto-fill from ptdata when possible.
    data = await lookup_nif(nif)
    legal = name_hint or data.get("legal_name") or f"NIF {nif}"
    payload = data.get("raw") if data.get("source") == "ptdata" else None
    created = (
        await session.execute(
            text(
                """
                INSERT INTO companies
                    (nif, legal_name, cae, address, status, ptdata_payload,
                     ptdata_fetched_at, monitored, monitoring_type)
                VALUES (:nif, :ln, :cae, :addr, :st, CAST(:pl AS JSONB),
                        CASE WHEN :pt THEN now() ELSE NULL END, TRUE, 'analysis')
                RETURNING id::text, nif, legal_name, monitoring_type
                """
            ),
            {
                "nif": nif,
                "ln": legal,
                "cae": data.get("cae"),
                "addr": data.get("address"),
                "st": data.get("status"),
                "pl": json.dumps(payload) if payload else None,
                "pt": bool(payload),
            },
        )
    ).first()
    await session.commit()
    return (
        {"id": created[0], "nif": created[1], "legal_name": created[2], "monitoring_type": created[3]},
        True,
    )


async def _scrape_and_ingest(company: dict[str, Any]) -> None:
    """Run CIRE + Distribuição for a single company, ingest into DB."""
    # CIRE
    try:
        matches = await scrape_cire_for_nif(company["nif"], company["id"], days="todos")
        if matches:
            async with AsyncSessionLocal() as s:
                for row in matches:
                    await upsert_process(s, row)
                await s.commit()
    except Exception as e:
        logger.warning("analysis CIRE failed for nif=%s: %s", company["nif"], e)

    # Distribuição (single POST per tribunal with party filter + wide date range)
    try:
        import asyncio
        from app.config import settings as _settings

        tribunals = await discover_tribunal_codes()
        search = short_search_term(company["legal_name"])
        if not search:
            return
        sem = asyncio.Semaphore(_settings.SCRAPE_CONCURRENCY)
        from datetime import date, timedelta

        date_from = date.today() - timedelta(days=365 * 5)  # 5y lookback
        date_to = date.today()

        all_matches: list[dict[str, Any]] = []

        async def worker(t: dict[str, str]) -> None:
            async with sem:
                try:
                    m = await scrape_distribuicao_range(
                        t["value"], t["label"], date_from, date_to,
                        [company], party_filter=search,
                    )
                    all_matches.extend(m)
                except Exception as e:
                    logger.warning(
                        "analysis Dist tribunal=%s failed: %s", t["label"], e
                    )

        await asyncio.gather(*(worker(t) for t in tribunals))

        if all_matches:
            async with AsyncSessionLocal() as s:
                for r in all_matches:
                    await upsert_process(s, r)
                await s.commit()
    except Exception as e:
        logger.warning("analysis Distribuição failed for nif=%s: %s", company["nif"], e)


async def _compute_metrics(
    session: AsyncSession, company_id: str
) -> dict[str, Any]:
    totals = (
        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 '180 days')::date) AS d180
                FROM processes WHERE company_id = :cid
                """
            ),
            {"cid": company_id},
        )
    ).first()
    by_source_rows = (
        await session.execute(
            text(
                "SELECT source, count(*) FROM processes WHERE company_id = :cid GROUP BY source"
            ),
            {"cid": company_id},
        )
    ).all()
    by_source = {r[0]: r[1] for r in by_source_rows}
    timeline_rows = (
        await session.execute(
            text(
                """
                SELECT to_char(date_trunc('month', date_filed), 'YYYY-MM') AS month,
                       count(*) AS c
                FROM processes
                WHERE company_id = :cid
                  AND date_filed >= (now() - interval '24 months')::date
                GROUP BY month
                ORDER BY month
                """
            ),
            {"cid": company_id},
        )
    ).all()
    timeline = [{"month": r[0], "count": int(r[1])} for r in timeline_rows]
    risk_score, risk_reasons = await compute_risk(session, company_id)
    return {
        "total_processes": totals[0],
        "last_30d": totals[1],
        "last_6m": totals[2],
        "by_source": by_source,
        "timeline": timeline,
        "risk_score": risk_score,
        "risk_reasons": risk_reasons,
    }


async def run_analysis(nif: str, name_hint: str | None = None) -> dict[str, Any]:
    if not is_valid_nif(nif):
        raise ValueError("invalid NIF")

    async with AsyncSessionLocal() as session:
        company, created = await _ensure_company(session, nif, name_hint)

    # Log start
    async with AsyncSessionLocal() as session:
        log_id = (
            await session.execute(
                text(
                    """
                    INSERT INTO scraping_logs (source, started_at, status, params)
                    VALUES ('analysis', now(), 'running',
                            CAST(:p AS JSONB))
                    RETURNING id::text
                    """
                ),
                {"p": json.dumps({"nif": nif, "company_id": company["id"]})},
            )
        ).first()[0]
        await session.commit()

    try:
        await _scrape_and_ingest(company)
        async with AsyncSessionLocal() as session:
            metrics = await _compute_metrics(session, company["id"])
            await update_company_risk(session, company["id"])
            await session.execute(
                text(
                    "UPDATE scraping_logs SET finished_at = now(), status = 'ok', rows_seen = :n WHERE id = :id"
                ),
                {"id": log_id, "n": metrics["total_processes"]},
            )
            await session.commit()
    except Exception as e:
        logger.exception("analysis failed: %s", e)
        async with AsyncSessionLocal() as session:
            await session.execute(
                text(
                    "UPDATE scraping_logs SET finished_at = now(), status = 'error', error = :e WHERE id = :id"
                ),
                {"id": log_id, "e": str(e)[:2000]},
            )
            await session.commit()
        raise

    return {
        "company_id": company["id"],
        "nif": company["nif"],
        "legal_name": company["legal_name"],
        "monitoring_type": company["monitoring_type"],
        "metrics": metrics,
        "created": created,
    }
