"""Federação entre instalações do DNS Shield ISP.

Duas faces:
  * painel (``/federacao``) — cadastro de parceiros, token de publicação e
    sincronização manual;
  * API (``/api/federacao/listas``) — endpoint consumido pelos parceiros,
    autenticado por Bearer token (não usa a sessão do painel).

Desenvolvedor: Euquias Junior <junior@jbinfo.com.br>
"""
from __future__ import annotations

import hmac

from flask import Blueprint, flash, jsonify, redirect, render_template, request, url_for

from .. import audit, federacao, licenca
from ..auth import login_required
from ..config import get_config

bp = Blueprint("federacao", __name__, url_prefix="/federacao")
api = Blueprint("federacao_api", __name__, url_prefix="/api/federacao")


@bp.get("/")
@login_required
def index():
    return render_template(
        "federacao.html",
        pub=federacao.get_publicacao(),
        peers=federacao.listar_peers(),
        cfg=get_config(),
    )


@bp.post("/publicacao")
@login_required
@licenca.requer_modulo("federacao", "federacao.index")
def save_publicacao():
    raw_freq = (request.form.get("frequencia") or "360").strip()
    try:
        frequencia = int(raw_freq)
    except ValueError:
        flash("A frequência deve ser um número de minutos.", "error")
        return redirect(url_for("federacao.index"))
    if frequencia < 15:
        flash("A frequência mínima é de 15 minutos.", "error")
        return redirect(url_for("federacao.index"))

    federacao.save_publicacao(
        publicar=request.form.get("publicar") == "on",
        publicar_liberacoes=request.form.get("publicar_liberacoes") == "on",
        consumir=request.form.get("consumir") == "on",
        frequencia=frequencia,
        remover_ausentes=request.form.get("remover_ausentes") == "on",
    )
    audit.log(
        "federacao_config",
        f"publicar={request.form.get('publicar') == 'on'}; "
        f"consumir={request.form.get('consumir') == 'on'}; frequencia={frequencia}min",
    )
    flash("Configuração da federação salva.", "success")
    return redirect(url_for("federacao.index"))


@bp.post("/token")
@login_required
@licenca.requer_modulo("federacao", "federacao.index")
def rotate_token():
    federacao.rotacionar_token()
    audit.log("federacao_token", "token de publicação regenerado")
    flash("Novo token de publicação gerado. Reenvie-o aos provedores parceiros.", "success")
    return redirect(url_for("federacao.index"))


@bp.post("/parceiros")
@login_required
@licenca.requer_modulo("federacao", "federacao.index")
def add_peer():
    nome = (request.form.get("nome") or "").strip()
    url = (request.form.get("url") or "").strip()
    token = (request.form.get("token") or "").strip()

    if not nome:
        flash("Informe o nome do provedor parceiro.", "error")
        return redirect(url_for("federacao.index"))
    if not url.startswith(("http://", "https://")):
        flash("A URL do parceiro deve começar com http:// ou https://.", "error")
        return redirect(url_for("federacao.index"))

    try:
        peer_id = federacao.adicionar_peer(
            nome=nome,
            url=url,
            token=token,
            importar_liberacoes=request.form.get("importar_liberacoes") == "on",
            enabled=request.form.get("enabled") == "on",
        )
    except federacao.FederacaoError as exc:
        flash(str(exc), "error")
        return redirect(url_for("federacao.index"))

    audit.log("federacao_peer_incluido", f"#{peer_id} {nome} | {url}")
    flash(f"Parceiro '{nome}' cadastrado.", "success")
    return redirect(url_for("federacao.index"))


@bp.post("/parceiros/<int:peer_id>/remover")
@login_required
def remove_peer(peer_id: int):
    peer = federacao.remover_peer(peer_id)
    if peer is None:
        flash("Parceiro não encontrado.", "error")
        return redirect(url_for("federacao.index"))
    audit.log("federacao_peer_removido", f"#{peer_id} {peer.get('nome')}")
    flash(
        f"Parceiro '{peer.get('nome')}' removido junto com os domínios que ele havia enviado.",
        "success",
    )
    try:
        from .. import rpz

        rpz.sync_zone()
    except Exception as exc:  # noqa: BLE001 - zona é revalidada na próxima sync
        flash(f"Atenção: a zona RPZ não recarregou ({exc}).", "error")
    return redirect(url_for("federacao.index"))


@bp.post("/sincronizar")
@login_required
@licenca.requer_modulo("federacao", "federacao.index")
def sync_now():
    peer_id = (request.form.get("peer_id") or "").strip()
    try:
        if peer_id:
            peer = federacao.get_peer(int(peer_id))
            if peer is None:
                flash("Parceiro não encontrado.", "error")
                return redirect(url_for("federacao.index"))
            result = federacao.sync_peer(peer)
            from .. import rpz

            try:
                rpz.sync_zone()
            except rpz.RPZError as exc:
                flash(f"Atenção: a zona RPZ não recarregou ({exc}).", "error")
            flash(f"{result.nome}: {result.mensagem}", "success")
        else:
            resultados = federacao.sync_all()
            flash(
                "Federação sincronizada: "
                + " | ".join(f"{r.nome}: {r.mensagem}" for r in resultados),
                "success",
            )
    except federacao.FederacaoError as exc:
        flash(f"Falha na sincronização da federação: {exc}", "error")
    return redirect(url_for("federacao.index"))


# ------------------------------------------------------------------------- API


@api.get("/listas")
def listas():
    """Entrega as listas locais aos parceiros autorizados."""
    if not licenca.modulo_ativo("federacao"):
        return jsonify({"erro": "modulo_nao_licenciado"}), 403

    pub = federacao.get_publicacao()
    if not int(pub.get("publicar") or 0):
        return jsonify({"erro": "publicacao_desabilitada"}), 404

    token = (pub.get("token_publicacao") or "").strip()
    header = request.headers.get("Authorization", "")
    enviado = header[7:].strip() if header.lower().startswith("bearer ") else (
        request.headers.get("X-Federacao-Token", "").strip()
    )
    if not token or not enviado or not hmac.compare_digest(token, enviado):
        return jsonify({"erro": "nao_autorizado"}), 401

    return jsonify(federacao.payload_publicacao())
