from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict
import logging
from urllib.parse import urljoin

import httpx

from core.config import settings
from .schemas import FacturaCoveRequest


logger = logging.getLogger(__name__)


@dataclass
class CoveExternalResult:
    """Resultado normalizado de POST /generar-factura-cove contra el API de VU."""

    status: str
    message: str | None = None
    cove_number: str | None = None
    vucem_operation_num: str | None = None
    # XML de COVE devuelto por VU codificado en base64 (campo envio_base64 del result)
    cove_xml_base64: str | None = None
    raw_response: Dict[str, Any] | None = None


class CoveExternalService:
    """
    Cliente del API externo de COVE.

    Implementación real:
      - POST /api/v1/factura-cove/generar-factura-cove para iniciar proceso.
      - GET /api/v1/factura-cove/status/{task_id} para consultar estado.
    """

    def __init__(self) -> None:
        self.base_url = settings.COVE_API_URL.strip()
        self.verify_ssl = settings.COVE_API_VERIFY_SSL

    def generate_cove(self, payload: FacturaCoveRequest) -> CoveExternalResult:
        """
        Llama al endpoint externo /api/v1/factura-cove/generar-factura-cove
        con el FacturaCoveRequest completo y retorna el resultado tal como
        lo reporta el servicio remoto.
        """
        url = f"{self.base_url.rstrip('/')}/api/v1/factura-cove/generar-factura-cove"
        json_payload = payload.model_dump(mode="json")
        configuracion_vu = json_payload.get("configuracion_vu") or {}
        logger.info(
            "Sending COVE payload: invoice=%s rfc_vu=%s clave_fiel_len=%s clave_ws_len=%s cer_len=%s key_len=%s",
            json_payload.get("numero_factura"),
            configuracion_vu.get("rfc_usuario_vu"),
            len(configuracion_vu.get("clave_fiel") or ""),
            len(configuracion_vu.get("clave_webservice") or ""),
            len(configuracion_vu.get("archivo_cer_base64") or ""),
            len(configuracion_vu.get("archivo_key_base64") or ""),
        )

        with httpx.Client(timeout=30.0, verify=self.verify_ssl, follow_redirects=False) as client:
            resp = client.post(url, json=json_payload)
            if resp.status_code in {301, 302, 307, 308} and resp.headers.get("location"):
                redirected_url = urljoin(url, resp.headers["location"])
                logger.info("COVE generate redirect: %s -> %s", url, redirected_url)
                resp = client.post(redirected_url, json=json_payload)

        # Intentar parsear JSON siempre, incluso en errores 4xx/5xx
        try:
            data: Dict[str, Any] = resp.json()
        except Exception:
            data = {}

        # Si el servicio externo respondió con error (por ejemplo 422 Validation Error),
        # devolvemos un resultado de error rico en información para que la UI pueda
        # mostrar el detalle completo.
        if resp.status_code >= 400:
            # Intentar construir un mensaje amigable
            message = None
            if isinstance(data, dict):
                message = data.get("message") or data.get("mensaje")
                if not message and "detail" in data:
                    # FastAPI ValidationError-style: detail: [{loc, msg, type}, ...]
                    try:
                        parts = [str(d.get("msg")) for d in data["detail"] if isinstance(d, dict)]
                        message = "; ".join([p for p in parts if p])
                    except Exception:
                        pass
                if not message and isinstance(data.get("errores"), list):
                    # Formato API VU: { mensaje, errores: [{ campo, mensaje, tipo_error }] }
                    parts: list[str] = []
                    for e in data["errores"]:
                        if not isinstance(e, dict):
                            continue
                        campo = e.get("campo") or ""
                        msg = e.get("mensaje") or ""
                        if campo and msg:
                            parts.append(f"{campo}: {msg}")
                        elif msg:
                            parts.append(msg)
                    if parts:
                        message = "; ".join(parts)
            if not message:
                message = resp.text or f"HTTP {resp.status_code}"

            status = "validation_error" if resp.status_code == 422 else "error"

            return CoveExternalResult(
                status=status,
                message=message,
                cove_number=None,
                vucem_operation_num=None,
                raw_response={
                    "status_code": resp.status_code,
                    "body": data,
                },
            )

        # 2xx: según la especificación del servicio externo, al menos devuelve:
        # { "task_id": "...", "status": "...", "message": "..." }
        raw_status = str(data.get("status") or "queued")
        message = data.get("message")
        external_task_id = data.get("task_id")

        # Si VU respondió "queued/external_queued/success" pero no task_id, la respuesta
        # no es utilizable para polling; tratarlo como error explícito.
        if raw_status.lower() in {"queued", "external_queued", "success"} and not external_task_id:
            return CoveExternalResult(
                status="error",
                message=(
                    "Respuesta inválida del servicio COVE: no se recibió task_id "
                    "para consultar el estado."
                ),
                cove_number=None,
                vucem_operation_num=None,
                raw_response={
                    "status_code": resp.status_code,
                    "body": data,
                },
            )

        # Normalizar el status de VU a un valor interno que active el polling cuando procede.
        # El API de VU ha devuelto distintos valores según la versión:
        #   - "PENDING" / "STARTED" con task_id → tarea encolada, se debe hacer polling.
        #   - "error" con mensaje "Factura COVE iniciada para" → mismo caso (versión anterior).
        #   - cualquier otro valor → se pasa sin modificar.
        normalized_status = raw_status.lower()
        if normalized_status in {"pending", "started"} and external_task_id:
            status = "external_queued"
        elif (
            normalized_status == "error"
            and isinstance(message, str)
            and "Factura COVE iniciada para" in message
        ):
            status = "external_queued"
        else:
            status = raw_status

        # El número de COVE normalmente se obtendrá vía /status/{task_id}; por ahora
        # lo dejamos en None y exponemos la respuesta completa para inspección en UI.
        return CoveExternalResult(
            status=status,
            message=message,
            cove_number=None,
            vucem_operation_num=None,
            raw_response={
                "task_id": external_task_id,
                "status": status,
                "message": message,
                "raw": data,
            },
        )

    def get_status(self, task_id: str) -> Dict[str, Any]:
        """
        Consulta el endpoint externo /api/v1/factura-cove/status/{task_id}
        y devuelve el JSON de progreso/resultado tal cual lo envía el servicio.

        Ejemplo de respuesta esperada (simplificada):
        {
          "task_id": "...",
          "state": "PROGRESS" | "SUCCESS" | "FAILURE",
          "result": null | {...},
          "error": null | "...",
          "progress": {
            "current_step": "Consultando respuesta COVE con número de operación",
            "progress": 10.5,
            "total_steps": 12,
            "task_id": "...",
            "numero_operacion": "306658625"
          }
        }
        """
        url = f"{self.base_url.rstrip('/')}/api/v1/factura-cove/status/{task_id}"

        with httpx.Client(timeout=30.0, verify=self.verify_ssl, follow_redirects=False) as client:
            resp = client.get(url)
            if resp.status_code in {301, 302, 307, 308} and resp.headers.get("location"):
                redirected_url = urljoin(url, resp.headers["location"])
                logger.info("COVE status redirect: %s -> %s", url, redirected_url)
                resp = client.get(redirected_url)

        try:
            data: Dict[str, Any] = resp.json()
        except Exception:
            data = {}

        # Adjuntar metadatos mínimos de respuesta HTTP
        data.setdefault("status_code", resp.status_code)

        return data

