"""Definições gerais, com a cadeia de decisão dos avisos por email num sítio só.

A ordem é sempre esta, e está aqui em vez de espalhada pelo SQL de cada despacho
porque é onde estes sistemas costumam mentir:

1. **interruptor geral** — desligado, ninguém recebe nada, seja qual for o resto;
2. **excepção da empresa** (`companies.alerts_enabled`) — cala uma empresa
   concreta mesmo que o tipo dela esteja ligado;
3. **defeito do tipo** — a matriz internas/concorrentes/clientes/análise/related;
4. **destinatários activos** em `alerts_config`.

Uma nota sobre precedência que não é óbvia: um hit de insolvência pode existir por
via da `insolvency_watchlist` (é um cliente) **e** por via da empresa. Nesse caso
manda o defeito de clientes, não o de análise — quem pôs o NIF na watchlist quer
ser avisado, e a mesma entidade estar também marcada como análise não é uma
instrução para calar.
"""
import json
import logging
from typing import Any

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

logger = logging.getLogger(__name__)

DEFAULTS: dict[str, Any] = {
    "email_notifications_enabled": True,
    "default_alert_email": "geral@segunor.pt",
    "alert_defaults_by_type": {
        "internal": True,
        "competitor": True,
        "client": True,
        "analysis": False,
        "related": False,
    },
}


async def get_all(session: AsyncSession) -> dict[str, Any]:
    rows = (await session.execute(text("SELECT key, value FROM app_settings"))).all()
    out = dict(DEFAULTS)
    for key, value in rows:
        out[key] = value
    return out


async def get(session: AsyncSession, key: str) -> Any:
    row = (
        await session.execute(
            text("SELECT value FROM app_settings WHERE key = :k"), {"k": key}
        )
    ).first()
    return row[0] if row else DEFAULTS.get(key)


async def set_many(
    session: AsyncSession, values: dict[str, Any], user_id: str | None = None
) -> int:
    changed = 0
    for key, value in values.items():
        if key not in DEFAULTS:
            continue
        await session.execute(
            text(
                """
                INSERT INTO app_settings (key, value, updated_by)
                VALUES (:k, CAST(:v AS JSONB), CAST(:uid AS UUID))
                ON CONFLICT (key) DO UPDATE SET
                    value = EXCLUDED.value, updated_at = now(), updated_by = EXCLUDED.updated_by
                """
            ),
            {"k": key, "v": json.dumps(value), "uid": user_id},
        )
        changed += 1
    return changed


async def emails_enabled(session: AsyncSession) -> bool:
    """O interruptor geral. Acima de tudo o resto."""
    value = await get(session, "email_notifications_enabled")
    return bool(value)


async def notifiable_nifs_sql() -> str:
    """SQL reutilizável: os NIFs que **podem** gerar aviso.

    Devolve-se como texto para os despachos o usarem dentro das suas queries em
    vez de replicarem a regra. Um NIF entra se:

    * está na watchlist activa (clientes vindos do Sabichão), **ou**
    * é uma empresa com `alerts_enabled` e cujo tipo está ligado na matriz.
    """
    return """
        SELECT w.nif FROM insolvency_watchlist w WHERE w.active
         UNION
        SELECT c.nif FROM companies c
          JOIN app_settings s ON s.key = 'alert_defaults_by_type'
         WHERE c.alerts_enabled
           AND COALESCE((s.value ->> c.monitoring_type)::boolean, FALSE)
    """


async def test_recipient_default(session: AsyncSession) -> str | None:
    """O botão de teste **não** usa o destinatário pré-definido por defeito.

    Numa sessão anterior um teste saiu para o `geral@segunor.pt` a sério e chegou
    lá um aviso de insolvência de uma empresa que nem era cliente. Um teste tem de
    ser inofensivo por construção, não por cuidado de quem o carrega.
    """
    return None
