from __future__ import annotations

import re
from collections.abc import Iterable
from dataclasses import dataclass
from typing import Dict, List, Mapping, Optional

try:
    import tldextract  # type: ignore
except Exception:  # pragma: no cover - optional dependency
    tldextract = None  # type: ignore


@dataclass(frozen=True)
class ContradictionRule:
    key: str
    severity: float

    def triggered(self, facts: Mapping[str, object]) -> bool:
        raise NotImplementedError


class DissolvedVsLiveSite(ContradictionRule):
    def triggered(self, facts: Mapping[str, object]) -> bool:
        return facts.get("status") == "dissolved" and bool(facts.get("domain_verified"))


class AuthorityNameDisagree(ContradictionRule):
    def triggered(self, facts: Mapping[str, object]) -> bool:
        names = facts.get("legal_names_authority") or []
        if not isinstance(names, Iterable):
            return False
        uniq = {_normalize_legal_name(str(name)) for name in names if name}
        return len(uniq) > 1


class TickerDomainDisagree(ContradictionRule):
    def triggered(self, facts: Mapping[str, object]) -> bool:
        return facts.get("ticker_match") is False and bool(facts.get("domain_verified"))


class ParentChildConflict(ContradictionRule):
    def triggered(self, facts: Mapping[str, object]) -> bool:
        return facts.get("hierarchy_consistency") is False


class GeographicMismatch(ContradictionRule):
    _GENERIC_HINTS = {"COM", "NET", "ORG", "IO", "AI"}

    def triggered(self, facts: Mapping[str, object]) -> bool:
        domain = facts.get("domain")
        jurisdiction = facts.get("jurisdiction")
        if not isinstance(domain, str) or not isinstance(jurisdiction, str):
            return False
        hint = _country_hint(domain)
        if not hint or hint in self._GENERIC_HINTS:
            return False
        return hint != jurisdiction.strip().upper()


DEFAULT_RULES: List[ContradictionRule] = [
    DissolvedVsLiveSite("dissolved_vs_live_site", 0.8),
    AuthorityNameDisagree("authority_name_disagree", 0.4),
    TickerDomainDisagree("ticker_domain_disagree", 0.5),
    ParentChildConflict("parent_child_conflict", 0.6),
    GeographicMismatch("geographic_mismatch", 0.35),
]


def detect_contradictions(
    facts: Mapping[str, object], rules: Iterable[ContradictionRule] = DEFAULT_RULES
) -> List[Dict[str, object]]:
    """Return contradictions triggered for a given entity fact bundle."""
    hits: List[Dict[str, object]] = []
    for rule in rules:
        try:
            if rule.triggered(facts):
                hits.append(
                    {
                        "rule_key": rule.key,
                        "severity": rule.severity,
                        "details": facts,
                    }
                )
        except Exception:
            continue
    return hits


_LEGAL_SUFFIX_RE = re.compile(
    r"\b(inc|ltd|llc|corp|corporation|plc|ag|gmbh|sarl|sa|bv|nv)\b\.?", re.IGNORECASE
)
_WHITESPACE_RE = re.compile(r"\s+")


def _normalize_legal_name(name: str) -> str:
    normalized = _LEGAL_SUFFIX_RE.sub("", name.lower())
    normalized = _WHITESPACE_RE.sub(" ", normalized)
    return normalized.strip()


def _country_hint(domain: str) -> Optional[str]:
    if not domain:
        return None
    if tldextract is not None:
        extracted = tldextract.extract(domain)
        suffix = extracted.suffix or ""
        if not suffix:
            return None
        return suffix.split(".")[-1].upper()
    match = re.search(r"\.([a-z]{2})$", domain.lower())
    if not match:
        return None
    return match.group(1).upper()
