"""ModifierCompatibilityValidator — detects invalid modifier combinations."""

from __future__ import annotations

from collections import Counter

from claim_validator.constants import Severity
from claim_validator.models.claim import ClaimData
from claim_validator.models.results import Finding, ValidatorOutput
from claim_validator.validators.base import BaseValidator

_X_MODIFIERS = frozenset({"XE", "XS", "XP", "XU"})


class ModifierCompatibilityValidator(BaseValidator):
    """Validates modifier combinations on claim service lines.

    Rules:
      1. Modifier 59 should not be used with X-modifiers (XE, XS, XP, XU).
      2. Bilateral modifier 50 should not duplicate a separate bilateral line.
      3. Modifiers 54 and 55 cannot appear on the same line.
      4. Modifiers TC and 26 cannot appear on the same line.
      5. Duplicate modifiers on a single line.
    """

    name = "ModifierCompatibilityValidator"

    def validate(self, claim: ClaimData) -> ValidatorOutput:
        findings: list[Finding] = []

        for idx, line in enumerate(claim.lines, start=1):
            mods = [m.strip().upper() for m in line.modifiers if m]
            self._check_59_with_x_modifiers(mods, idx, findings)
            self._check_conflicting_global_modifiers(mods, idx, findings)
            self._check_tc_professional_conflict(mods, idx, findings)
            self._check_duplicate_modifiers(mods, idx, findings)

        self._check_bilateral_duplicates(claim, findings)

        return self._make_output(findings)

    def _check_59_with_x_modifiers(
        self,
        mods: list[str],
        line_number: int,
        findings: list[Finding],
    ) -> None:
        mod_set = set(mods)
        if "59" in mod_set and mod_set & _X_MODIFIERS:
            findings.append(
                self._make_finding(
                    code="MODIFIER_59_WITH_X_MODIFIER",
                    message=(
                        "Modifier 59 should not be used with X-modifiers "
                        "(XE, XS, XP, XU) — X-modifiers are more specific "
                        "replacements for 59"
                    ),
                    severity=Severity.ERROR,
                    field_name="modifiers",
                    line_number=line_number,
                    suggestion=(
                        "Remove modifier 59 and keep only the appropriate "
                        "X-modifier"
                    ),
                ),
            )

    def _check_conflicting_global_modifiers(
        self,
        mods: list[str],
        line_number: int,
        findings: list[Finding],
    ) -> None:
        mod_set = set(mods)
        if "54" in mod_set and "55" in mod_set:
            findings.append(
                self._make_finding(
                    code="CONFLICTING_GLOBAL_MODIFIERS",
                    message=(
                        "Modifiers 54 (surgical care only) and "
                        "55 (postoperative care only) cannot appear "
                        "on the same line"
                    ),
                    severity=Severity.ERROR,
                    field_name="modifiers",
                    line_number=line_number,
                    suggestion=(
                        "Use modifier 54 for surgical care only or "
                        "55 for postoperative care only, not both"
                    ),
                ),
            )

    def _check_tc_professional_conflict(
        self,
        mods: list[str],
        line_number: int,
        findings: list[Finding],
    ) -> None:
        mod_set = set(mods)
        if "TC" in mod_set and "26" in mod_set:
            findings.append(
                self._make_finding(
                    code="TC_PROFESSIONAL_CONFLICT",
                    message=(
                        "Modifier TC (technical component) and "
                        "26 (professional component) cannot be billed "
                        "together on the same line"
                    ),
                    severity=Severity.ERROR,
                    field_name="modifiers",
                    line_number=line_number,
                    suggestion=(
                        "Bill the technical component (TC) and "
                        "professional component (26) on separate lines"
                    ),
                ),
            )

    def _check_duplicate_modifiers(
        self,
        mods: list[str],
        line_number: int,
        findings: list[Finding],
    ) -> None:
        counts = Counter(mods)
        for mod, count in counts.items():
            if count > 1:
                findings.append(
                    self._make_finding(
                        code="DUPLICATE_MODIFIER",
                        message=(
                            f"Modifier {mod} appears more than once "
                            f"on this service line"
                        ),
                        severity=Severity.WARNING,
                        field_name="modifiers",
                        line_number=line_number,
                        suggestion=(
                            f"Remove the duplicate modifier {mod}"
                        ),
                    ),
                )

    def _check_bilateral_duplicates(
        self,
        claim: ClaimData,
        findings: list[Finding],
    ) -> None:
        """Flag lines with modifier 50 where the same procedure code
        appears on another line (same date) without modifier 50."""
        if not claim.lines:
            return

        for idx, line in enumerate(claim.lines, start=1):
            mods = {m.strip().upper() for m in line.modifiers if m}
            if "50" not in mods:
                continue

            proc = line.procedure_code.strip().upper()
            line_date = getattr(line, "service_date_from", None)

            for other_idx, other in enumerate(claim.lines, start=1):
                if other_idx == idx:
                    continue
                other_proc = other.procedure_code.strip().upper()
                other_mods = {m.strip().upper() for m in other.modifiers if m}
                other_date = getattr(other, "service_date_from", None)

                if (
                    other_proc == proc
                    and "50" not in other_mods
                    and line_date == other_date
                ):
                    findings.append(
                        self._make_finding(
                            code="BILATERAL_MODIFIER_DUPLICATE",
                            message=(
                                "Modifier 50 (bilateral) detected — "
                                "ensure this is not duplicated with a "
                                "separate bilateral line"
                            ),
                            severity=Severity.WARNING,
                            field_name="modifiers",
                            line_number=idx,
                            suggestion=(
                                "Either use modifier 50 on a single "
                                "line or bill two separate lines "
                                "without modifier 50"
                            ),
                        ),
                    )
                    break
