"""Adaptive identity transform profiling and quality detection."""

from __future__ import annotations

import os
from dataclasses import dataclass
from statistics import mean
from typing import Dict, Iterable, List, Optional

import pandas as pd

from fmatch.core.b2c_bloom_filter import is_b2c_domain


def _dns_env_default() -> bool:
    value = os.getenv("FM_COMPANY_TO_DOMAIN_DNS", "false")
    return isinstance(value, str) and value.strip().lower() == "true"


@dataclass(frozen=True)
class IdentityTransformProfile:
    """Configuration bundle for identity transforms."""

    name: str
    dns_enabled: bool
    dns_timeout: float = 2.0
    max_candidates: int = 5
    use_registry: bool = True
    transforms_enabled: bool = True
    circuit_threshold: float = 0.3
    circuit_sample_size: int = 50

    def company_to_domain_options(self) -> Dict[str, object]:
        return {
            "use_dns": self.dns_enabled,
            "dns_timeout": self.dns_timeout,
            "max_candidates": self.max_candidates,
            "use_registry": self.use_registry,
        }

    def circuit_breaker_options(self) -> Dict[str, object]:
        if not self.dns_enabled:
            return {}
        return {
            "threshold": self.circuit_threshold,
            "sample_size": self.circuit_sample_size,
        }


@dataclass
class DNSCircuitBreaker:
    threshold: float = 0.3
    sample_size: int = 50

    failures: int = 0
    attempts: int = 0
    is_open: bool = False

    def record(self, success: bool) -> None:
        if self.is_open:
            return
        self.attempts += 1
        if not success:
            self.failures += 1
        if self.attempts >= max(1, self.sample_size):
            failure_rate = (self.failures / self.attempts) if self.attempts else 0.0
            if failure_rate > self.threshold:
                self.is_open = True

    def reset(self) -> None:
        self.failures = 0
        self.attempts = 0
        self.is_open = False

    def to_dict(self) -> Dict[str, float]:
        return {
            "failures": self.failures,
            "attempts": self.attempts,
            "threshold": self.threshold,
            "is_open": self.is_open,
        }


_TRANSFORM_PROFILES: Dict[str, IdentityTransformProfile] = {
    "enterprise": IdentityTransformProfile(
        name="enterprise",
        dns_enabled=_dns_env_default(),
        dns_timeout=2.0,
        max_candidates=5,
        use_registry=True,
        circuit_threshold=0.35,
        circuit_sample_size=60,
    ),
    "low_quality_b2c": IdentityTransformProfile(
        name="low_quality_b2c",
        dns_enabled=False,
        dns_timeout=0.5,
        max_candidates=2,
        use_registry=True,
    ),
    "garbage": IdentityTransformProfile(
        name="garbage",
        dns_enabled=False,
        transforms_enabled=False,
        use_registry=False,
    ),
}


@dataclass
class IdentityQualitySignals:
    single_word_pct: float
    numeric_pct: float
    avg_length: float
    b2c_domain_pct: float
    person_name_pct: float
    sample_size: int


def detect_data_quality(
    df: pd.DataFrame, sample_size: int = 100
) -> IdentityQualitySignals:
    sample = df.head(sample_size)
    names = _extract_name_candidates(sample)
    total = len(names)
    if total == 0:
        return IdentityQualitySignals(0.0, 0.0, 0.0, 0.0, 0.0, 0)

    cleaned_names = [n.strip() for n in names if isinstance(n, str)]
    single_word = sum(1 for n in cleaned_names if n and len(n.split()) == 1)
    numeric = sum(1 for n in cleaned_names if n.isdigit())
    avg_len = mean(len(n) for n in cleaned_names) if cleaned_names else 0.0
    person_like = sum(1 for n in cleaned_names if _looks_like_person_name(n))

    b2c_domains = 0
    domain_values = _extract_domain_candidates(sample)
    for dom in domain_values:
        if dom and is_b2c_domain(dom):
            b2c_domains += 1

    domain_total = len(domain_values) or 1
    return IdentityQualitySignals(
        single_word_pct=single_word / total,
        numeric_pct=numeric / total,
        avg_length=avg_len,
        b2c_domain_pct=b2c_domains / domain_total,
        person_name_pct=person_like / total,
        sample_size=total,
    )


def select_profile(
    signals: IdentityQualitySignals, override: Optional[str] = None
) -> IdentityTransformProfile:
    if override:
        return _TRANSFORM_PROFILES.get(override, _TRANSFORM_PROFILES["enterprise"])

    if signals.sample_size == 0:
        return _TRANSFORM_PROFILES["enterprise"]

    if signals.avg_length < 5 or signals.numeric_pct > 0.3:
        return _TRANSFORM_PROFILES["garbage"]

    if signals.b2c_domain_pct > 0.4 or signals.person_name_pct > 0.3:
        return _TRANSFORM_PROFILES["low_quality_b2c"]

    return _TRANSFORM_PROFILES["enterprise"]


def list_profiles() -> Dict[str, IdentityTransformProfile]:
    return dict(_TRANSFORM_PROFILES)


def _extract_name_candidates(df: pd.DataFrame) -> List[str]:
    candidate_cols = _find_columns(
        df.columns, ("company", "account", "name", "organization", "organisation")
    )
    if not candidate_cols:
        candidate_cols = _find_object_columns(df)
    values: List[str] = []
    for col in candidate_cols:
        series = df[col].dropna().astype(str)
        values.extend(series.tolist())
        if len(values) >= 200:
            break
    return values[:200]


def _extract_domain_candidates(df: pd.DataFrame) -> List[str]:
    domain_cols = _find_columns(df.columns, ("email", "domain", "website", "url"))
    domains: List[str] = []
    for col in domain_cols:
        series = df[col].dropna().astype(str)
        for value in series:
            dom = _normalize_domain_candidate(value)
            if dom:
                domains.append(dom)
        if len(domains) >= 200:
            break
    return domains[:200]


def _find_columns(columns: Iterable[str], keywords: Iterable[str]) -> List[str]:
    keywords_lower = [k.lower() for k in keywords]
    selected: List[str] = []
    for col in columns:
        lower = col.lower()
        if any(key in lower for key in keywords_lower):
            selected.append(col)
    return selected


def _find_object_columns(df: pd.DataFrame) -> List[str]:
    return [col for col in df.columns if df[col].dtype == object]


def _normalize_domain_candidate(value: str) -> Optional[str]:
    if not value:
        return None
    value = value.strip().lower()
    if "@" in value:
        value = value.split("@", 1)[-1]
    if value.startswith("http"):
        value = value.split("//", 1)[-1]
    value = value.split("/", 1)[0]
    return value or None


def _looks_like_person_name(value: str) -> bool:
    parts = [p for p in value.split() if p]
    if not parts or len(parts) > 3:
        return False
    if any(any(ch.isdigit() for ch in part) for part in parts):
        return False
    capitalized = sum(1 for part in parts if part[0].isalpha() and part[0].isupper())
    lower = sum(1 for part in parts if part.islower())
    return capitalized >= len(parts) - 1 and lower == 0
