"""Transparent 4-field weighted scoring for Inbox candidate pairs.

SP6 T5.

Weights (sum to 100):
  - patient control number match (exact, normalized)        40
  - service date proximity (±3 days linear decay)           25
  - charge amount proximity (±10% linear decay)             20
  - provider NPI exact match                                15
"""
from __future__ import annotations

from dataclasses import dataclass
from datetime import date
from decimal import Decimal
from typing import Protocol


class _ScorableClaim(Protocol):
    patient_control_number: str
    service_date_from: date | None
    charge_amount: Decimal
    provider_npi: str | None


class _ScorableRemit(Protocol):
    patient_control_number: str
    service_date: date | None
    charge_amount: Decimal
    rendering_provider_npi: str | None


@dataclass(frozen=True)
class ScoreBreakdown:
    patient: float
    date: float
    amount: float
    provider: float

    @property
    def total(self) -> int:
        return round(
            self.patient * 40
            + self.date * 25
            + self.amount * 20
            + self.provider * 15
        )

    @property
    def tier(self) -> str:
        if self.total >= 75:
            return "strong"
        if self.total >= 50:
            return "weak"
        return "hidden"


def _norm_pcn(s: str) -> str:
    return (s or "").strip().lower().lstrip("0")


def _date_score(claim_date: date | None, remit_date: date | None) -> float:
    if claim_date is None or remit_date is None:
        return 0.0
    delta = abs((claim_date - remit_date).days)
    if delta > 3:
        return 0.0
    return 1.0 - (delta / 3.0)


def _amount_score(claim_amt: Decimal, remit_amt: Decimal) -> float:
    if claim_amt <= 0:
        return 0.0
    diff = abs(claim_amt - remit_amt)
    threshold = claim_amt * Decimal("0.10")
    if diff > threshold:
        return 0.0
    # Normalize diff within threshold to 0..1
    return float(Decimal("1.0") - (diff / threshold))


def _provider_score(claim_npi: str | None, remit_npi: str | None) -> float:
    if not claim_npi or not remit_npi:
        return 0.0
    return 1.0 if claim_npi.strip() == remit_npi.strip() else 0.0


def score_pair(claim: _ScorableClaim, remit: _ScorableRemit) -> ScoreBreakdown:
    claim_pcn = _norm_pcn(claim.patient_control_number)
    remit_pcn = _norm_pcn(remit.patient_control_number)
    return ScoreBreakdown(
        patient=1.0 if claim_pcn and claim_pcn == remit_pcn else 0.0,
        date=_date_score(claim.service_date_from, remit.service_date),
        amount=_amount_score(claim.charge_amount, remit.charge_amount),
        provider=_provider_score(claim.provider_npi, remit.rendering_provider_npi),
    )
