"""Valores monetarios del pipeline clínico."""

from __future__ import annotations

import re
from decimal import Decimal, InvalidOperation
from typing import Any, ClassVar, Self

from pydantic import BaseModel, ConfigDict, field_validator


class MonetaryAmount(BaseModel):
    """Importe inmutable y normalizado, independiente del formato de origen."""

    model_config = ConfigDict(frozen=True)

    CENT: ClassVar[Decimal] = Decimal("0.01")
    amount: Decimal

    @field_validator("amount", mode="before")
    @classmethod
    def _parse_amount(cls, value: Any) -> Decimal:
        if isinstance(value, Decimal):
            return value.quantize(cls.CENT)
        if isinstance(value, (int, float)) and not isinstance(value, bool):
            return Decimal(str(value)).quantize(cls.CENT)

        raw = re.sub(r"\s+", "", str(value or "")).upper()
        if not raw:
            raise ValueError("El importe está vacío")

        negative = raw.startswith("(") and raw.endswith(")")
        if negative:
            raw = raw[1:-1]
        raw = raw.replace("COP", "").replace("$", "")
        if raw.startswith(('+', '-')):
            negative = negative or raw.startswith("-")
            raw = raw[1:]
        if not raw or not re.fullmatch(r"\d+(?:[.,]\d+)*", raw):
            raise ValueError("El importe no tiene un formato monetario válido")

        normalized = cls._normalize_separators(raw)
        try:
            amount = Decimal(normalized)
        except InvalidOperation as exc:
            raise ValueError("El importe no es numérico") from exc
        if negative:
            amount = -amount
        return amount.quantize(cls.CENT)

    @classmethod
    def _normalize_separators(cls, raw: str) -> str:
        separators = [separator for separator in (".", ",") if separator in raw]
        if not separators:
            return raw

        if len(separators) == 2:
            decimal_separator = "." if raw.rfind(".") > raw.rfind(",") else ","
            integer_part, decimal_part = raw.rsplit(decimal_separator, 1)
            if len(decimal_part) not in {1, 2}:
                raise ValueError("La parte decimal monetaria debe tener uno o dos dígitos")
            thousands_separator = "," if decimal_separator == "." else "."
            if not cls._valid_grouped_integer(integer_part, thousands_separator):
                raise ValueError("Los separadores de miles son inconsistentes")
            return f"{integer_part.replace(thousands_separator, '')}.{decimal_part}"

        separator = separators[0]
        groups = raw.split(separator)
        if any(not group for group in groups):
            raise ValueError("El importe contiene separadores vacíos")
        if len(groups) == 2 and len(groups[-1]) in {1, 2}:
            return f"{groups[0]}.{groups[1]}"
        if len(groups[-1]) == 3 and cls._valid_grouped_integer(raw, separator):
            return "".join(groups)
        raise ValueError("El separador monetario es ambiguo o inválido")

    @staticmethod
    def _valid_grouped_integer(value: str, separator: str) -> bool:
        groups = value.split(separator)
        return bool(groups and 1 <= len(groups[0]) <= 3 and all(len(group) == 3 for group in groups[1:]))

    @classmethod
    def try_parse(cls, value: Any) -> Self | None:
        try:
            return cls(amount=value)
        except (InvalidOperation, TypeError, ValueError):
            return None

    def within(self, other: Self, *, tolerance: Decimal | None = None) -> bool:
        return abs(self.amount - other.amount) <= (tolerance or self.CENT)

    def formatted(self) -> str:
        prefix = "-$" if self.amount < 0 else "$"
        return f"{prefix}{abs(self.amount):,.2f}"
