from __future__ import annotations

import base64
import logging
import os
import tempfile
import zipfile
from dataclasses import dataclass
from typing import Any, Optional

import requests
import xmltodict
from cryptography.hazmat.primitives.serialization import (
    Encoding,
    NoEncryption,
    PrivateFormat,
)
from cryptography.hazmat.primitives.serialization.pkcs12 import (
    load_key_and_certificates,
)


logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class SetApiConfig:
    debug: bool = False
    timeout: int = 90
    save_request_file: Optional[str] = None


def _normalize_xml(xml: str) -> str:
    return (
        (xml or "")
        .replace("\r\n", "")
        .replace("\n", "")
        .replace("\t", "")
        .replace(">    <", "><")
        .replace(">  <", "><")
        .strip()
    )


import re


def _strip_xml_declaration(xml: str) -> str:
    if not xml:
        return xml
    # Robust regex stripping
    return re.sub(
        r"^\s*<\?xml.*?\?>", "", xml, count=1, flags=re.DOTALL | re.IGNORECASE
    ).strip()


def _extract_pem_cert_and_key(
    pfx_bytes: bytes, password: Optional[str]
) -> tuple[bytes, bytes]:
    if password is not None:
        pwd = password.encode("utf-8")
    else:
        pwd = None

    key, cert, _chain = load_key_and_certificates(pfx_bytes, pwd)
    if key is None or cert is None:
        raise ValueError(
            "PKCS#12 inválido: no se pudo extraer clave privada o certificado"
        )

    cert_pem = cert.public_bytes(Encoding.PEM)
    key_pem = key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())
    return cert_pem, key_pem


def _post_soap(
    *, url: str, soap_xml: str, cert_pem: bytes, key_pem: bytes, config: SetApiConfig
) -> dict[str, Any]:
    if config.save_request_file:
        with open(config.save_request_file, "w", encoding="utf-8") as f:
            f.write(soap_xml)

    # requests requiere paths a archivos para (cert, key)
    cert_file = tempfile.NamedTemporaryFile(delete=False)
    key_file = tempfile.NamedTemporaryFile(delete=False)
    try:
        cert_file.write(cert_pem)
        cert_file.flush()
        key_file.write(key_pem)
        key_file.flush()
        cert_file.close()
        key_file.close()

        # permisos similares a 0o600
        try:
            os.chmod(cert_file.name, 0o600)
            os.chmod(key_file.name, 0o600)
        except Exception:
            pass

        headers = {
            "User-Agent": "facturaSend",
            "Content-Type": "application/xml; charset=utf-8",
        }
        resp = requests.post(
            url,
            data=soap_xml.encode("utf-8"),
            headers=headers,
            timeout=config.timeout,
            cert=(cert_file.name, key_file.name),
        )

        text = resp.text or ""
        if resp.status_code != 200:
            # Igual que la lib TS: si hay XML en error, intentar parsear
            if text.strip().startswith("<?xml"):
                return xmltodict.parse(text)
            raise RuntimeError("Error de conexión con SIFEN")

        if text.strip().startswith("<?xml"):
            return xmltodict.parse(text)

        if text.strip().lower().startswith("<html"):
            raise RuntimeError("Error SIFEN BIG-IP logout page")

        raise RuntimeError(text)
    finally:
        try:
            os.unlink(cert_file.name)
        except Exception:
            pass
        try:
            os.unlink(key_file.name)
        except Exception:
            pass


def _soap_url(env: str, path: str) -> str:
    base = (
        "https://sifen.set.gov.py" if env == "prod" else "https://sifen-test.set.gov.py"
    )
    return f"{base}{path}"


def _body_dict(parsed: dict[str, Any]) -> dict[str, Any]:
    # xmltodict genera keys como 'env:Envelope'/'env:Body'
    env = parsed.get("env:Envelope") or parsed.get("Envelope") or parsed
    body = None
    if isinstance(env, dict):
        body = env.get("env:Body") or env.get("Body")
    if body is None:
        return {"raw": parsed}
    return body if isinstance(body, dict) else {"raw": body}


def consulta(
    *,
    id: int,
    cdc: str,
    env: str,
    pfx_bytes: bytes,
    password: Optional[str],
    config: Optional[SetApiConfig] = None,
) -> dict[str, Any]:
    cfg = config or SetApiConfig()
    cert_pem, key_pem = _extract_pem_cert_and_key(pfx_bytes, password)
    url = _soap_url(env, "/de/ws/consultas/consulta.wsdl")

    soap = f"""<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<env:Envelope xmlns:env=\"http://www.w3.org/2003/05/soap-envelope\">
  <env:Header/>
  <env:Body>
    <rEnviConsDeRequest xmlns=\"http://ekuatia.set.gov.py/sifen/xsd\">
      <dId>{id}</dId>
      <dCDC>{cdc}</dCDC>
    </rEnviConsDeRequest>
  </env:Body>
</env:Envelope>"""
    soap = _normalize_xml(soap)
    parsed = _post_soap(
        url=url, soap_xml=soap, cert_pem=cert_pem, key_pem=key_pem, config=cfg
    )
    out = _body_dict(parsed)
    out["id"] = id
    return out


def consulta_lote(
    *,
    id: int,
    numero_lote: int,
    env: str,
    pfx_bytes: bytes,
    password: Optional[str],
    config: Optional[SetApiConfig] = None,
) -> dict[str, Any]:
    cfg = config or SetApiConfig()
    cert_pem, key_pem = _extract_pem_cert_and_key(pfx_bytes, password)
    url = _soap_url(env, "/de/ws/consultas/consulta-lote.wsdl")

    soap = f"""<env:Envelope xmlns:env=\"http://www.w3.org/2003/05/soap-envelope\">
  <env:Header/>
  <env:Body>
    <rEnviConsLoteDe xmlns=\"http://ekuatia.set.gov.py/sifen/xsd\">
      <dId>{id}</dId>
      <dProtConsLote>{numero_lote}</dProtConsLote>
    </rEnviConsLoteDe>
  </env:Body>
</env:Envelope>"""
    soap = _normalize_xml(soap)
    parsed = _post_soap(
        url=url, soap_xml=soap, cert_pem=cert_pem, key_pem=key_pem, config=cfg
    )
    out = _body_dict(parsed)
    out["id"] = id
    return out


def consulta_ruc(
    *,
    id: int,
    ruc: str,
    env: str,
    pfx_bytes: bytes,
    password: Optional[str],
    config: Optional[SetApiConfig] = None,
) -> dict[str, Any]:
    logger.info(f"Iniciando consulta de RUC para ID: {id}, RUC: {ruc}, Ambiente: {env}")
    cfg = config or SetApiConfig()
    try:
        cert_pem, key_pem = _extract_pem_cert_and_key(pfx_bytes, password)
        url = _soap_url(env, "/de/ws/consultas/consulta-ruc.wsdl")

        ruc_sin_dv = ruc.split("-")[0]
        logger.debug(f"RUC sin DV: {ruc_sin_dv}")

        soap = f"""<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
      <env:Header/>
      <env:Body>
        <rEnviConsRUC xmlns="http://ekuatia.set.gov.py/sifen/xsd">
          <dId>{id}</dId>
          <dRUCCons>{ruc_sin_dv}</dRUCCons>
        </rEnviConsRUC>
      </env:Body>
    </env:Envelope>"""
        soap = _normalize_xml(soap)
        logger.debug(f"SOAP Request: {soap}")

        parsed = _post_soap(
            url=url, soap_xml=soap, cert_pem=cert_pem, key_pem=key_pem, config=cfg
        )
        logger.debug(f"SOAP Response: {parsed}")

        out = _body_dict(parsed)
        out["id"] = id
        logger.info(f"Consulta de RUC para ID: {id} completada exitosamente.")
        return out
    except Exception as e:
        logger.exception(f"Error en consulta_ruc para ID: {id}, RUC: {ruc}: {e}")
        raise


def recibe(
    *,
    id: int,
    xml: str,
    env: str,
    pfx_bytes: bytes,
    password: Optional[str],
    config: Optional[SetApiConfig] = None,
) -> dict[str, Any]:
    cfg = config or SetApiConfig()
    cert_pem, key_pem = _extract_pem_cert_and_key(pfx_bytes, password)
    url = _soap_url(env, "/de/ws/sync/recibe.wsdl")

    de_payload = _strip_xml_declaration(xml)
    print(f"DEBUG: Payload content (start): {de_payload[:60]}")  # Debug print

    soap = f"""<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<env:Envelope xmlns:env=\"http://www.w3.org/2003/05/soap-envelope\">
  <env:Header/>
  <env:Body>
    <rEnviDe xmlns=\"http://ekuatia.set.gov.py/sifen/xsd\">
      <dId>{id}</dId>
      <xDE>{de_payload}</xDE>
    </rEnviDe>
  </env:Body>
</env:Envelope>"""
    soap = _normalize_xml(soap)
    parsed = _post_soap(
        url=url, soap_xml=soap, cert_pem=cert_pem, key_pem=key_pem, config=cfg
    )
    out = _body_dict(parsed)
    out["id"] = id
    return out


def recibe_lote(
    *,
    id: int,
    xmls: list[str],
    env: str,
    pfx_bytes: bytes,
    password: Optional[str],
    config: Optional[SetApiConfig] = None,
) -> dict[str, Any]:
    cfg = config or SetApiConfig()
    if not xmls:
        raise ValueError(
            "No se envió datos en el array de Documentos electrónicos XMLs"
        )
    if len(xmls) > 50:
        raise ValueError(
            "Sólo se permiten un máximo de 50 Documentos electrónicos XML por lote"
        )

    cert_pem, key_pem = _extract_pem_cert_and_key(pfx_bytes, password)
    url = _soap_url(env, "/de/ws/async/recibe-lote.wsdl")

    r_lote = ["<rLoteDE>"]
    for x in xmls:
        r_lote.append(_strip_xml_declaration(x))
    r_lote.append("</rLoteDE>")
    r_lote_xml = _normalize_xml("\n".join(r_lote))

    buf = tempfile.SpooledTemporaryFile()
    try:
        with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as zf:
            zf.writestr(
                "xml_file.xml", f'<?xml version="1.0" encoding="UTF-8"?>{r_lote_xml}'
            )
        buf.seek(0)
        zip_b64 = base64.b64encode(buf.read()).decode("ascii")
    finally:
        buf.close()

    soap = f"""<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<env:Envelope xmlns:env=\"http://www.w3.org/2003/05/soap-envelope\">
  <env:Header/>
  <env:Body>
    <rEnvioLote xmlns=\"http://ekuatia.set.gov.py/sifen/xsd\">
      <dId>{id}</dId>
      <xDE>{zip_b64}</xDE>
    </rEnvioLote>
  </env:Body>
</env:Envelope>"""
    soap = _normalize_xml(soap)
    parsed = _post_soap(
        url=url, soap_xml=soap, cert_pem=cert_pem, key_pem=key_pem, config=cfg
    )
    out = _body_dict(parsed)
    out["id"] = id
    return out


def evento(
    *,
    id: int,
    xml: str,
    env: str,
    pfx_bytes: bytes,
    password: Optional[str],
    config: Optional[SetApiConfig] = None,
) -> dict[str, Any]:
    cfg = config or SetApiConfig()
    cert_pem, key_pem = _extract_pem_cert_and_key(pfx_bytes, password)
    url = _soap_url(env, "/de/ws/eventos/evento.wsdl")

    soap = _normalize_xml(xml)
    parsed = _post_soap(
        url=url, soap_xml=soap, cert_pem=cert_pem, key_pem=key_pem, config=cfg
    )
    out = _body_dict(parsed)
    out["id"] = id
    return out
