from __future__ import annotations

import logging
import datetime as dt
import time
import re
from typing import Annotated, Any

from fastapi import APIRouter, Depends, HTTPException, Request, status
from motor.motor_asyncio import AsyncIOMotorDatabase
from pymongo import ReturnDocument
from pydantic import BaseModel, Field

from fastcheck_api.app.api.dependencies import CurrentUser, require_permission
from fastcheck_api.app.core.mongodb import get_db
from fastcheck_api.app.services.dequienes_native_service import dequienes_native_service
from fastcheck_api.app.services.audit_service import AuditService
from fastcheck_api.app.services.risk_calculation_service import RiskCalculationService
from fastcheck_api.app.services.sheriff_v2_native_service import sheriff_v2_native_service
from fastcheck_api.app.utils.mongo import jsonable, sanitize_rut, to_object_id


router = APIRouter(prefix="/rut", tags=["rut"])
logger = logging.getLogger("fastcheck.rut")


class RutLookupRequest(BaseModel):
    rut: str = Field(examples=["12345678-9"])
    isMonitoring: bool = Field(default=False)
    isRefreshing: bool = Field(default=False)
    isPep: bool = Field(default=False)
    type: str = Field(default="individual")


def _as_list(value: Any) -> list[Any]:
    return value if isinstance(value, list) else []


def _safe_get(dct: Any, *path: str, default: Any = None) -> Any:
    cur = dct
    for key in path:
        if not isinstance(cur, dict):
            return default
        cur = cur.get(key)
    return default if cur is None else cur


def _extract_socios_from_diario_oficial(rut: str, diario_oficial: Any, limit: int = 200) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    seen: set[str] = set()
    for item in _as_list(diario_oficial):
        for socio in _as_list(_safe_get(item, "socios", default=[])):
            socio_rut = sanitize_rut(str(_safe_get(socio, "rut", default="") or ""))
            if not socio_rut or socio_rut == rut:
                continue
            if socio_rut in seen:
                continue
            seen.add(socio_rut)
            name = str(_safe_get(socio, "nombre", default="") or _safe_get(socio, "name", default="") or "").strip()
            out.append({"name": name or "N/A", "rut": socio_rut, "personType": None, "isNaturalPerson": None})
            if len(out) >= limit:
                return out
    return out


def _compute_is_pep_from_compliance(compliance: Any) -> bool:
    pep = _as_list(_safe_get(compliance, "pepChile", "coincidencias", default=[]))
    fam = _as_list(_safe_get(compliance, "familiaresPep", "coincidencias", default=[]))
    pub = _as_list(_safe_get(compliance, "funcionariosPublicos", "coincidencias", default=[]))
    return bool(pep or fam or pub)


def _simple_risk_assessment(filtered_details: dict[str, Any]) -> dict[str, Any]:
    compliance = filtered_details.get("compliance") or {}
    is_pep = _compute_is_pep_from_compliance(compliance)
    has_judicial = bool(filtered_details.get("civilCasesData") or filtered_details.get("laboralCasesData") or filtered_details.get("cobranzaCasesData"))
    has_labor_collection = bool(filtered_details.get("moraPrevisionalCasesData") or filtered_details.get("multaLaboralCasesData"))
    semaphore = "bajo"
    if is_pep:
        semaphore = "critico"
    elif has_judicial or has_labor_collection:
        semaphore = "alto"
    return {
        "riskSummary": {"semaphore": semaphore},
        "allRules": {
            "complianceRules": [],
            "legalRules": [],
            "capitalHumanoRules": [],
            "financieroTributarioRules": [],
        },
        "summaryDocumentMD": "",
    }


def _compute_is_pep_from_risk(risk_assessment: dict[str, Any] | None) -> bool:
    if not isinstance(risk_assessment, dict):
        return False
    rules = _safe_get(risk_assessment, "allRules", "complianceRules", default=[])
    for r in _as_list(rules):
        label = str(_safe_get(r, "label", default="") or "")
        if label in {"PEP Chile", "Familiares PEP"} and bool(_safe_get(r, "detected", default=False)):
            return True
    return False


_PROV_REPLACEMENTS: dict[str, str] = {
    "sheriff": "sProv",
    "dequienes": "dProv",
}
_PROV_PATTERN = re.compile("|".join(re.escape(k) for k in _PROV_REPLACEMENTS), re.IGNORECASE)


def _mask_providers_text(value: str) -> str:
    return _PROV_PATTERN.sub(lambda m: _PROV_REPLACEMENTS[m.group(0).lower()], value)


def _mask_providers_json(value: Any) -> Any:
    if isinstance(value, str):
        return _mask_providers_text(value)
    if isinstance(value, list):
        return [_mask_providers_json(v) for v in value]
    if isinstance(value, dict):
        out: dict[Any, Any] = {}
        for k, v in value.items():
            masked_key = _mask_providers_text(k) if isinstance(k, str) else k
            out[masked_key] = _mask_providers_json(v)
        return out
    return value


def _normalize_date(value: str | None) -> str | None:
    if not value:
        return None
    raw = str(value).strip()
    if not raw:
        return None
    if re.match(r"^\d{4}-\d{2}-\d{2}$", raw):
        return raw
    m = re.match(r"^(\d{2})-(\d{2})-(\d{4})$", raw)
    if m:
        dd, mm, yyyy = m.group(1), m.group(2), m.group(3)
        return f"{yyyy}-{mm}-{dd}"
    m = re.match(r"^(\d{2})/(\d{2})/(\d{4})$", raw)
    if m:
        dd, mm, yyyy = m.group(1), m.group(2), m.group(3)
        return f"{yyyy}-{mm}-{dd}"
    return None


def _strip_cache_info_json(value: Any) -> Any:
    if isinstance(value, list):
        return [_strip_cache_info_json(v) for v in value]
    if isinstance(value, dict):
        out: dict[Any, Any] = {}
        for k, v in value.items():
            if k == "_cache_info":
                continue
            out[k] = _strip_cache_info_json(v)
        return out
    return value


def _sanitize_result_payload(payload: Any) -> Any:
    return _mask_providers_json(_strip_cache_info_json(payload))


@router.post(
    "/lookup",
    summary="Lookup RUT",
    description=(
        "Runs a native RUT lookup using Sheriff V2 APIs and persists the result in MongoDB (same persistence model as the legacy Node implementation)."
    ),
)
async def lookup_rut(
    request: Request,
    payload: RutLookupRequest,
    user: Annotated[CurrentUser, Depends(require_permission("rut:lookup"))],
    db: Annotated[AsyncIOMotorDatabase, Depends(get_db)],
):
    start = time.perf_counter()
    rut = sanitize_rut(payload.rut)

    refresh = bool(payload.isMonitoring or payload.isRefreshing)
    now = dt.datetime.now(dt.timezone.utc)

    if refresh:
        await db["results"].update_many(
            {"rut": rut, "tenantId": user.tenant, "archived": {"$ne": True}},
            {"$set": {"archived": True, "archivedAt": now, "updatedAt": now}},
        )

    existing = await db["results"].find_one(
        {"rut": rut, "tenantId": user.tenant, "archived": {"$ne": True}},
        sort=[("createdAt", -1)],
    )
    if existing and not refresh:
        await AuditService.log_consulta(
            db=db,
            tenant_id=user.tenant,
            user_id=user.id,
            consulta_type="individual",
            rut=rut,
            endpoint="/client-api-v1/rut/lookup",
            response_status=status.HTTP_200_OK,
            request_data=jsonable(payload.model_dump()),
            response_data={"source": "cache"},
            processing_time_ms=int((time.perf_counter() - start) * 1000),
            credits_used=0,
            ip_address=request.client.host if request.client else None,
            user_agent=request.headers.get("user-agent"),
            metadata={"source": "cache"},
        )
        return _sanitize_result_payload(jsonable(existing))

    should_deduct = (not payload.isPep) and (payload.isRefreshing or not refresh)
    if should_deduct:
        tenant_oid = to_object_id(user.tenant)
        tenant_after = await db["tenants"].find_one_and_update(
            {"_id": tenant_oid, "creditBalance.availableCredits": {"$gte": 1}},
            {
                "$inc": {
                    "creditBalance.availableCredits": -1,
                    "creditBalance.totalCreditsUsed": 1,
                },
                "$set": {
                    "creditBalance.lastCreditOperation": now,
                    "updatedAt": now,
                },
            },
            return_document=ReturnDocument.AFTER,
        )
        if not tenant_after:
            await AuditService.log_consulta(
                db=db,
                tenant_id=user.tenant,
                user_id=user.id,
                consulta_type="individual",
                rut=rut,
                endpoint="/client-api-v1/rut/lookup",
                response_status=status.HTTP_402_PAYMENT_REQUIRED,
                request_data=jsonable(payload.model_dump()),
                processing_time_ms=int((time.perf_counter() - start) * 1000),
                credits_used=0,
                ip_address=request.client.host if request.client else None,
                user_agent=request.headers.get("user-agent"),
                metadata={"reason": "insufficient_credits"},
            )
            raise HTTPException(
                status_code=status.HTTP_402_PAYMENT_REQUIRED,
                detail={"message": "Insufficient credits", "availableCredits": int((tenant_after or {}).get("creditBalance", {}).get("availableCredits", 0))},
            )

        new_balance = int((tenant_after.get("creditBalance") or {}).get("availableCredits") or 0)
        await db["creditoperations"].insert_one(
            {
                "tenantId": tenant_oid,
                "userId": to_object_id(user.id),
                "operationType": "evaluation",
                "creditsChanged": -1,
                "balanceAfter": new_balance,
                "description": f"Valutazione singola per {rut}",
                "metadata": {"evaluationType": "single", "supplierRut": rut, "source": "fastcheck_api"},
                "createdAt": now,
            }
        )

    response_status = status.HTTP_200_OK
    error_message: str | None = None
    try:
        sheriff_payload = await sheriff_v2_native_service.query_rut(rut=rut, is_monitoring=payload.isMonitoring)
        masked_sheriff_payload = _mask_providers_json(sheriff_payload)

        resumen_data = _safe_get(masked_sheriff_payload, "resumen", "data", default={})
        data_sii = _safe_get(resumen_data, "identificacion", default={})
        credit_scoring = _safe_get(masked_sheriff_payload, "creditScore", "data", default={})
        compliance = _safe_get(masked_sheriff_payload, "compliance", "data", default={})

        filtered_details: dict[str, Any] = {
            "rut": _safe_get(resumen_data, "rut", default=rut),
            "razonSocial": _safe_get(data_sii, "razonSocial", default=None),
            "presentaActividades": _safe_get(data_sii, "presentaActividades", default=None),
            "inicioActividades": _safe_get(data_sii, "inicioActividades", default=None),
            "autorizadoMonedaExtranjera": _safe_get(data_sii, "autorizadoMonedaExtranjera", default=None),
            "empresaMenor": _safe_get(data_sii, "empresaMenor", default=None),
            "observaciones": _safe_get(data_sii, "observaciones", default=None),
            "notas": _safe_get(data_sii, "notas", default=None),
            "situacionActual": _safe_get(data_sii, "situacionActual", default=None),
            "fetchedAt": now.date().isoformat(),
            "creditScoringData": credit_scoring,
            "compliance": compliance,
            "boletinComercialSummary": _safe_get(masked_sheriff_payload, "resumen", "boletinComercial", default={}),
            "boletinLaboralSummary": _safe_get(resumen_data, "boletinLaboral", default={}),
            "boletinLaboralLastUpdate": _safe_get(resumen_data, "boletinLaboral", "ultimaActualizacion", default=None),
            "vehiclesSummary": _safe_get(resumen_data, "bienes", "vehiculos", default={}),
            "propertiesSummary": _safe_get(resumen_data, "bienes", "propiedades", default={}),
            "deudaBancariaSummary": _safe_get(masked_sheriff_payload, "resumen", "deudaBancaria", default={}),
            "mallaSocietariaData": _safe_get(masked_sheriff_payload, "legal", "mallaSocietaria", "data", default={}),
            "officialDiaryData": _safe_get(masked_sheriff_payload, "legal", "mallaSocietaria", "data", "diarioOficial", default=[]),
            "civilCasesData": _safe_get(masked_sheriff_payload, "judicial", "civil", "data", default={}),
            "laboralCasesData": _safe_get(masked_sheriff_payload, "judicial", "laboral", "data", default={}),
            "cobranzaCasesData": _safe_get(masked_sheriff_payload, "judicial", "cobranza", "data", default={}),
            "moraPrevisionalCasesData": _safe_get(masked_sheriff_payload, "cobranzaLaboral", "moraPrevisional", "data", default={}),
            "multaLaboralCasesData": _safe_get(masked_sheriff_payload, "cobranzaLaboral", "multaLaboral", "data", default={}),
        }

        compliance_person_type = "juridical" if bool(_safe_get(resumen_data, "esPersonaJuridica", default=False)) else "natural"
        filtered_details["compliancePersonType"] = compliance_person_type
        filtered_details["complianceSelectedRowsPepChile"] = _as_list(_safe_get(compliance, "pepChile", "coincidencias", default=[]))
        filtered_details["complianceSelectedRowsPublicOfficial"] = _as_list(
            _safe_get(compliance, "funcionariosPublicos", "coincidencias", default=[])
        )
        filtered_details["complianceSelectedRowsPepChileFamily"] = _as_list(_safe_get(compliance, "familiaresPep", "coincidencias", default=[]))
        filtered_details["complianceSelectedRowsPenal"] = _as_list(_safe_get(compliance, "penal", "coincidencias", default=[]))
        filtered_details["complianceSelectedlistasInternacionales"] = _as_list(
            _safe_get(compliance, "listasInternacionales", "coincidencias", default=[])
        )

        company_general_info: dict[str, Any] = {
            "rapresentanteLegal": "N/D",
            "rapresentanteLegalRut": "N/D",
            "inicioActividades": filtered_details.get("inicioActividades") or "",
            "fechaDeConstitucion": "",
            "empresaMenor": filtered_details.get("empresaMenor") or "",
            "razonSocial": filtered_details.get("razonSocial") or "",
            "socios": [],
            "administradores": [],
            "rangoVentas": None,
            "tamanoEmpresa": None,
            "iconImpresa": None,
            "cantidadTrabajadores": None,
        }

        diario = filtered_details.get("officialDiaryData")
        company_general_info["socios"] = _extract_socios_from_diario_oficial(rut, diario)

        if not payload.isPep:
            try:
                deq_relationships = await dequienes_native_service.relationships(
                    rut=rut,
                    distance=2,
                    relationship_direction="BOTH",
                    one_path_per_node=False,
                )
                filtered_details["dequienesRelationships"] = _mask_providers_json(_strip_cache_info_json(deq_relationships))
            except Exception:
                logger.exception("Dequienes relationships failed")
            try:
                deq_legal = await dequienes_native_service.legal_events(rut=rut)
                filtered_details["dequienesLegalEvents"] = _mask_providers_json(_strip_cache_info_json(deq_legal))
                raw_date = deq_legal.get("constitution_date") if isinstance(deq_legal, dict) else None
                normalized = _normalize_date(raw_date if isinstance(raw_date, str) else None)
                if normalized and not company_general_info.get("fechaDeConstitucion"):
                    company_general_info["fechaDeConstitucion"] = normalized
            except Exception:
                logger.exception("Dequienes legal events failed")

        if not company_general_info.get("fechaDeConstitucion"):
            company_general_info["fechaDeConstitucion"] = "No hay informacion"

        risk_assessment = await RiskCalculationService.calculate_risk(
            db,
            tenant_id=user.tenant,
            rut=rut,
            sheriff_v2_data=masked_sheriff_payload,
            filtered_details=filtered_details,
            company_general_info=company_general_info,
            is_pep_only=payload.isPep,
        )
        is_pep = _compute_is_pep_from_risk(risk_assessment) or _compute_is_pep_from_compliance(compliance)

        processing_ms = int((time.perf_counter() - start) * 1000)
        result_doc: dict[str, Any] = {
            "rut": rut,
            "tenantId": user.tenant,
            "queryType": "derived_socio_pep" if payload.isPep else "primary",
            "sheriffLogData": {
                "rut": rut,
                "tenantId": user.tenant,
                "fetchedAt": now,
                "companyGeneralInfo": company_general_info if not payload.isPep else None,
                "filteredDetails": filtered_details,
                "rawSheriffV2": masked_sheriff_payload,
            },
            "details": filtered_details,
            "companyGeneralInfo": None if payload.isPep else company_general_info,
            "riskAssessment": risk_assessment,
            "isPep": bool(is_pep),
            "userEmail": user.email,
            "status": 200,
            "type": payload.type,
            "processingTime": f"{processing_ms}ms",
            "creditsConsumed": 1 if should_deduct else 0,
            "archived": False,
            "createdAt": now,
            "updatedAt": now,
        }

        insert = await db["results"].insert_one(result_doc)
        stored = await db["results"].find_one({"_id": insert.inserted_id})
        response_payload = jsonable(stored) if stored else jsonable({**result_doc, "_id": str(insert.inserted_id)})
        response_payload = _sanitize_result_payload(response_payload)

        await AuditService.log_consulta(
            db=db,
            tenant_id=user.tenant,
            user_id=user.id,
            consulta_type="individual",
            rut=rut,
            endpoint="/client-api-v1/rut/lookup",
            response_status=response_status,
            request_data=jsonable(payload.model_dump()),
            response_data={"resultId": response_payload.get("_id")},
            processing_time_ms=processing_ms,
            credits_used=1 if should_deduct else 0,
            ip_address=request.client.host if request.client else None,
            user_agent=request.headers.get("user-agent"),
            metadata={"evaluationType": "single", "source": "fastcheck_api"},
        )
        return response_payload
    except Exception as e:
        response_status = status.HTTP_502_BAD_GATEWAY
        error_message = str(e)
        logger.exception(f"Native lookup failed error={error_message[:500]}")
        await AuditService.log_consulta(
            db=db,
            tenant_id=user.tenant,
            user_id=user.id,
            consulta_type="individual",
            rut=rut,
            endpoint="/client-api-v1/rut/lookup",
            response_status=response_status,
            request_data=jsonable(payload.model_dump()),
            error_message=error_message[:2000],
            processing_time_ms=int((time.perf_counter() - start) * 1000),
            credits_used=1 if should_deduct else 0,
            ip_address=request.client.host if request.client else None,
            user_agent=request.headers.get("user-agent"),
            metadata={"evaluationType": "single", "source": "fastcheck_api"},
        )
        raise HTTPException(status_code=response_status, detail={"message": "Native lookup failed", "error": error_message[:2000]})
