"""Configurações do provedor, IPs do sinkhole e regeração da página de bloqueio."""
from __future__ import annotations

import configparser
import html
import ipaddress
import re
from pathlib import Path

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

from .. import audit
from ..auth import login_required
from ..config import CONFIG_PATH, get_config
from ..db import get_db, get_provider

bp = Blueprint("settings", __name__, url_prefix="/configuracoes")

SINKHOLE_TEMPLATE = Path(__file__).resolve().parents[2] / "templates" / "sinkhole" / "index.html"

OPTIONAL_LINES = {
    "LEGAL_NAME": '<div>{value}</div>',
    "CNPJ": '<div>CNPJ: {value}</div>',
    "SUPPORT_EMAIL": '<div>Contato: {value}</div>',
}


def render_sinkhole(provider: dict) -> str:
    template = SINKHOLE_TEMPLATE.read_text(encoding="utf-8")

    # linhas opcionais desaparecem quando o campo estiver vazio
    for key, markup in OPTIONAL_LINES.items():
        raw = (provider.get(key.lower()) or "").strip()
        block = markup.format(value=html.escape(raw)) if raw else ""
        template = re.sub(
            rf"[ \t]*<!--IF_{key}-->.*?<!--END_{key}-->\n?",
            (f"      {block}\n" if block else ""),
            template,
            flags=re.DOTALL,
        )

    values = {
        "{{TRADE_NAME}}": html.escape(provider.get("trade_name") or "Provedor"),
        "{{RETURN_URL}}": html.escape(
            provider.get("return_url") or "https://example.com.br", quote=True
        ),
    }
    for key, value in values.items():
        template = template.replace(key, value)
    return template


def write_sinkhole(provider: dict) -> str | None:
    cfg = get_config()
    try:
        root = Path(cfg.sinkhole_root)
        root.mkdir(parents=True, exist_ok=True)
        (root / "index.html").write_text(render_sinkhole(provider), encoding="utf-8")
        return None
    except OSError as exc:
        return str(exc)


def update_config_ini(values: dict[str, str]) -> str | None:
    """Persiste chaves na seção [dns] do config.ini preservando o restante."""
    try:
        parser = configparser.ConfigParser()
        parser.read(CONFIG_PATH)
        if not parser.has_section("dns"):
            parser.add_section("dns")
        for key, value in values.items():
            parser.set("dns", key, value)
        with CONFIG_PATH.open("w", encoding="utf-8") as fh:
            parser.write(fh)
        get_config(reload=True)
        return None
    except (OSError, configparser.Error) as exc:
        return str(exc)


@bp.get("/")
@login_required
def index():
    from .. import anablock

    provider = get_provider()
    return render_template(
        "settings.html",
        data=provider,
        cfg=get_config(),
        anablock=anablock.get_settings(),
        provider_incompleto=not (provider.get("trade_name") or "").strip(),
    )


@bp.post("/")
@login_required
def save():
    fields = {
        "trade_name": (request.form.get("trade_name") or "").strip(),
        "legal_name": (request.form.get("legal_name") or "").strip(),
        "cnpj": (request.form.get("cnpj") or "").strip(),
        "support_email": (request.form.get("support_email") or "").strip(),
        "return_url": (request.form.get("return_url") or "").strip(),
    }

    if not fields["trade_name"]:
        flash("O nome fantasia é obrigatório.", "error")
        return redirect(url_for("settings.index"))
    if not fields["return_url"].startswith(("http://", "https://")):
        flash("A URL de retorno deve começar com http:// ou https://.", "error")
        return redirect(url_for("settings.index"))
    if fields["support_email"] and "@" not in fields["support_email"]:
        flash("E-mail de suporte inválido.", "error")
        return redirect(url_for("settings.index"))

    db = get_db()
    db.execute(
        "UPDATE provider_settings SET trade_name = ?, legal_name = ?, cnpj = ?, "
        "support_email = ?, return_url = ?, updated_at = datetime('now') WHERE id = 1",
        (
            fields["trade_name"],
            fields["legal_name"],
            fields["cnpj"],
            fields["support_email"],
            fields["return_url"],
        ),
    )
    db.commit()

    audit.log("config_alterada", "; ".join(f"{k}={v}" for k, v in fields.items()))
    error = write_sinkhole(fields)
    if error:
        flash(f"Configurações salvas, mas a página de bloqueio não foi regravada: {error}", "error")
    else:
        flash("Configurações salvas e página de bloqueio atualizada.", "success")
    return redirect(url_for("settings.index"))


@bp.post("/sinkhole")
@login_required
def save_sinkhole():
    """Altera os IPs de destino dos domínios bloqueados e regrava a zona RPZ."""
    from .. import rpz

    ipv4 = (request.form.get("sinkhole_ip") or "").strip()
    ipv6 = (request.form.get("sinkhole_ipv6") or "").strip()

    try:
        if ipaddress.ip_address(ipv4).version != 4:
            raise ValueError
    except ValueError:
        flash("Informe um endereço IPv4 válido para a página de bloqueio.", "error")
        return redirect(url_for("settings.index"))

    if ipv6:
        try:
            if ipaddress.ip_address(ipv6).version != 6:
                raise ValueError
        except ValueError:
            flash("Endereço IPv6 inválido. Deixe em branco para não responder AAAA.", "error")
            return redirect(url_for("settings.index"))

    error = update_config_ini({"sinkhole_ip": ipv4, "sinkhole_ipv6": ipv6})
    if error:
        flash(f"Não foi possível gravar {CONFIG_PATH}: {error}", "error")
        return redirect(url_for("settings.index"))

    audit.log("sinkhole_alterado", f"IPv4={ipv4}; IPv6={ipv6 or '(nenhum)'}")
    try:
        rpz.sync_zone()
        flash(
            f"IPs do sinkhole atualizados e zona RPZ regravada apontando para {ipv4}.",
            "success",
        )
    except rpz.RPZError as exc:
        flash(f"IPs salvos, mas a zona RPZ não recarregou: {exc}", "error")
    return redirect(url_for("settings.index"))
