"""
B2C Domain Engine Integration
=============================
Integrates B2C domain filtering into the fuzzy matching engine's preprocessing pipeline.
"""

import re
import logging
import pandas as pd
from typing import Optional, Dict, Any, Tuple
from pathlib import Path
from functools import lru_cache

from fmatch.core.b2c_bloom_filter import is_b2c_domain, get_b2c_checker

log = logging.getLogger(__name__)


def extract_domain_from_email(email: str) -> Optional[str]:
    """
    Extract domain from email address.

    Args:
        email: Email address string

    Returns:
        Domain part or None if invalid
    """
    if not email or not isinstance(email, str):
        return None

    email = email.strip().lower()

    # Basic email regex
    match = re.match(r"^[^@]+@([^@\s]+)$", email)
    if match:
        return match.group(1)

    return None


def extract_domain_from_mixed(value: str) -> Optional[str]:
    """
    Extract domain from mixed field that might contain email or domain.

    Handles cases like:
    - "john@example.com"
    - "example.com"
    - "www.example.com"
    - "https://example.com"
    - "Example Corp (example.com)"
    """
    if not value or not isinstance(value, str):
        return None

    value = value.strip().lower()

    # Try email first
    domain = extract_domain_from_email(value)
    if domain:
        return domain

    # Clean up common patterns
    # Remove protocol
    value = re.sub(r"^https?://", "", value)
    # Remove www
    value = re.sub(r"^www\.", "", value)
    # Remove paths
    value = re.sub(r"/.*$", "", value)
    # Remove port
    value = re.sub(r":\d+$", "", value)

    # Extract domain from parentheses or after common separators
    patterns = [
        r"\(([a-z0-9.-]+\.[a-z]{2,})\)",  # (example.com)
        r"[@\s]([a-z0-9.-]+\.[a-z]{2,})",  # @example.com or space
        r"^([a-z0-9.-]+\.[a-z]{2,})$",  # Just domain
    ]

    for pattern in patterns:
        match = re.search(pattern, value)
        if match:
            domain = match.group(1)
            # Basic validation
            if "." in domain and len(domain) > 3:
                return domain

    return None


@lru_cache(maxsize=100_000)
def _cleanse_single_domain_cached(domain_value: str, strip_b2c: bool) -> Optional[str]:
    """Cached implementation backing cleanse_single_domain."""
    if not isinstance(domain_value, str) or not domain_value:
        return None

    domain = str(domain_value).lower().strip()

    domain = re.sub(r"^https?://", "", domain)
    domain = re.sub(r"^www\.", "", domain)

    if "@" in domain:
        match = re.search(r"@([^@\s]+)$", domain)
        if match:
            domain = match.group(1)

    domain = domain.rstrip("/")
    domain = re.sub(r"[^\w.-]", "", domain)

    if not domain:
        return None

    if strip_b2c and is_b2c_domain(domain):
        return None

    return domain


def cleanse_single_domain(domain_value: str, strip_b2c: bool = True) -> Optional[str]:
    """Clean a single domain value with cached normalization."""
    return _cleanse_single_domain_cached(domain_value, strip_b2c)


def clear_domain_cache() -> None:
    """Reset cached domain normalization results."""
    _cleanse_single_domain_cached.cache_clear()


def cleanse_domain_field(
    series: pd.Series,
    *,
    strip_b2c: bool = True,
    extract_from_email: bool = True,
    company_name_fallback: Optional[pd.Series] = None,
) -> pd.Series:
    """
    Cleanse domain field with B2C filtering.

    This is the main preprocessing function that should be called by the engine.

    Args:
        series: Domain/email column to cleanse
        strip_b2c: Whether to remove B2C domains
        extract_from_email: Whether to extract domain from email addresses
        company_name_fallback: Company name series for fallback when domain is stripped

    Returns:
        Cleaned series with B2C domains removed
    """
    log.info(
        f"Cleansing domain field '{series.name}' "
        f"(strip_b2c={strip_b2c}, extract_from_email={extract_from_email})"
    )

    # Stats tracking
    stats = {
        "total": len(series),
        "non_null": series.notna().sum(),
        "extracted_domains": 0,
        "b2c_stripped": 0,
        "fallback_used": 0,
    }

    def _clean_single(value: str) -> str:
        """Clean a single value."""
        if pd.isna(value) or not value:
            return ""

        value = str(value).strip()

        # Handle special values
        if value.lower() in {"", ".", "-", "n/a", "na", "none", "null"}:
            return ""

        # Extract domain if needed
        domain = None
        if extract_from_email:
            domain = extract_domain_from_mixed(value)
            if domain:
                stats["extracted_domains"] += 1
        else:
            # Just clean the value
            domain = value.split()[0].split(";")[0].split(",")[0].lower().strip()

        if not domain:
            return ""

        # Normalize via cached cleanser before B2C check
        normalized = cleanse_single_domain(domain, strip_b2c=False)
        if not normalized:
            return ""

        if strip_b2c and is_b2c_domain(normalized):
            stats["b2c_stripped"] += 1
            return ""  # Will trigger fallback if available

        return normalized

    # Apply cleaning
    cleaned = series.astype("string").map(_clean_single)

    # Apply company name fallback for empty values
    if company_name_fallback is not None and strip_b2c:
        mask = (cleaned == "") & company_name_fallback.notna()
        fallback_values = company_name_fallback[mask].apply(_company_name_fallback)
        cleaned[mask] = fallback_values
        stats["fallback_used"] = mask.sum()

    # Log statistics
    log.info(
        f"Domain cleansing complete: "
        f"extracted={stats['extracted_domains']:,}, "
        f"b2c_stripped={stats['b2c_stripped']:,}, "
        f"fallback_used={stats['fallback_used']:,}"
    )

    return cleaned


def _company_name_fallback(company_name: str) -> str:
    """
    Generate a blocking key from company name when domain is stripped.

    This ensures records still participate in blocking even without domain.
    """
    if pd.isna(company_name) or not company_name:
        return ""

    # Clean and normalize
    name = str(company_name).lower().strip()

    # Remove common suffixes
    suffixes = [
        "incorporated",
        "inc",
        "corporation",
        "corp",
        "limited",
        "ltd",
        "llc",
        "company",
        "co",
        "group",
        "partners",
        "associates",
    ]

    for suffix in suffixes:
        name = re.sub(rf"\b{suffix}\b\.?$", "", name).strip()

    # Remove special characters
    name = re.sub(r"[^a-z0-9\s]", " ", name)
    name = re.sub(r"\s+", " ", name).strip()

    # Take first significant word
    words = name.split()
    for word in words:
        if len(word) >= 3:  # Skip short words
            return word

    return words[0] if words else ""


class B2CFilterConfig:
    """Configuration for B2C filtering in preprocessing."""

    def __init__(
        self,
        enabled: bool = True,
        domain_file: Optional[Path] = None,
        filter_file: Optional[Path] = None,
        extract_from_email: bool = True,
        company_fallback_enabled: bool = True,
        stats_tracking: bool = True,
    ):
        self.enabled = enabled
        self.domain_file = domain_file
        self.filter_file = filter_file
        self.extract_from_email = extract_from_email
        self.company_fallback_enabled = company_fallback_enabled
        self.stats_tracking = stats_tracking

        # Initialize checker if enabled
        if self.enabled:
            self._init_checker()

    def _init_checker(self):
        """Initialize the B2C checker with custom paths if provided."""
        if self.domain_file or self.filter_file:
            from .b2c_bloom_filter import B2CDomainChecker

            # Create custom checker instance
            self.checker = B2CDomainChecker(
                domain_file=self.domain_file, filter_file=self.filter_file
            )
        else:
            # Use global singleton
            self.checker = get_b2c_checker()

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        return {
            "enabled": self.enabled,
            "domain_file": str(self.domain_file) if self.domain_file else None,
            "filter_file": str(self.filter_file) if self.filter_file else None,
            "extract_from_email": self.extract_from_email,
            "company_fallback_enabled": self.company_fallback_enabled,
            "stats_tracking": self.stats_tracking,
        }


def preprocess_dataframe_with_b2c(
    df: pd.DataFrame,
    mappings: list,
    b2c_config: Optional[B2CFilterConfig] = None,
    **other_preprocessing_args,
) -> Tuple[pd.DataFrame, Dict[str, Any]]:
    """
    Enhanced preprocessing with B2C domain filtering.

    This wraps the existing preprocess_dataframe function to add B2C filtering.
    """
    if b2c_config is None:
        b2c_config = B2CFilterConfig()

    preprocessing_stats = {}

    # Apply B2C filtering to domain columns if enabled
    if b2c_config.enabled:
        domain_columns = _identify_domain_columns(df, mappings)

        for col_info in domain_columns:
            col_name = col_info["column"]
            is_mapped = col_info["is_mapped"]

            if col_name not in df.columns:
                continue

            log.info(f"Applying B2C filter to column: {col_name}")

            # Get company name column for fallback (if available)
            company_col = None
            if b2c_config.company_fallback_enabled:
                company_col = _find_company_column(df, col_name)

            # Apply cleansing
            original_values = df[col_name].copy()
            df[col_name] = cleanse_domain_field(
                df[col_name],
                strip_b2c=True,
                extract_from_email=b2c_config.extract_from_email,
                company_name_fallback=df[company_col] if company_col else None,
            )

            # Track stats
            if b2c_config.stats_tracking:
                preprocessing_stats[f"b2c_filter_{col_name}"] = {
                    "original_non_null": original_values.notna().sum(),
                    "cleaned_non_null": df[col_name].notna().sum(),
                    "values_removed": (original_values != df[col_name]).sum(),
                }

    # Call original preprocessing function
    # Note: You'll need to import/call your existing preprocessing here
    # df, other_stats = original_preprocess_dataframe(df, mappings, **other_preprocessing_args)

    # Merge stats
    # preprocessing_stats.update(other_stats)

    return df, preprocessing_stats


def _identify_domain_columns(df: pd.DataFrame, mappings: list) -> list:
    """
    Identify columns that likely contain domains or emails.

    Returns list of dicts with column info.
    """
    domain_columns = []

    # Check mapped columns first
    mapped_cols = {m["source"] for m in mappings if "source" in m}

    # Keywords that suggest domain/email content
    domain_keywords = [
        "domain",
        "website",
        "url",
        "site",
        "web",
        "email",
        "mail",
        "contact",
    ]

    for col in df.columns:
        col_lower = col.lower()

        # Check if column name suggests domain/email
        is_domain_col = any(keyword in col_lower for keyword in domain_keywords)

        if is_domain_col:
            # Sample data to verify
            sample = df[col].dropna().head(100)
            if not sample.empty:
                # Check if values look like domains/emails
                domain_pattern = r"[a-z0-9.-]+\.[a-z]{2,}|@[a-z0-9.-]+\.[a-z]{2,}"
                matches = sample.astype(str).str.contains(
                    domain_pattern, case=False, na=False
                )

                if matches.mean() > 0.3:  # At least 30% match pattern
                    domain_columns.append(
                        {
                            "column": col,
                            "is_mapped": col in mapped_cols,
                            "confidence": matches.mean(),
                        }
                    )

    return domain_columns


def _find_company_column(df: pd.DataFrame, domain_col: str) -> Optional[str]:
    """Find the most likely company name column for fallback."""
    # Keywords that suggest company name
    company_keywords = [
        "company",
        "account",
        "organization",
        "org",
        "business",
        "customer",
        "client",
        "name",
    ]

    candidates = []

    for col in df.columns:
        if col == domain_col:
            continue

        col_lower = col.lower()

        # Score based on keyword matches
        score = sum(1 for keyword in company_keywords if keyword in col_lower)

        if score > 0:
            candidates.append((col, score))

    if candidates:
        # Return column with highest score
        candidates.sort(key=lambda x: x[1], reverse=True)
        return candidates[0][0]

    return None


# Example integration test
if __name__ == "__main__":
    # Create test data
    test_df = pd.DataFrame(
        {
            "company_name": [
                "Acme Corp",
                "Gmail User",
                "Yahoo Person",
                "Big Company Inc",
            ],
            "email": [
                "contact@acme.com",
                "user@gmail.com",
                "person@yahoo.com",
                "info@bigco.com",
            ],
            "website": ["acme.com", "gmail.com", "yahoo.com", "bigco.com"],
        }
    )

    print("Original data:")
    print(test_df)

    # Test domain extraction and B2C filtering
    print("\nTesting domain field cleansing:")

    # Email field
    cleaned_email = cleanse_domain_field(
        test_df["email"],
        strip_b2c=True,
        extract_from_email=True,
        company_name_fallback=test_df["company_name"],
    )
    print(f"\nCleaned email domains: {list(cleaned_email)}")

    # Website field
    cleaned_website = cleanse_domain_field(
        test_df["website"],
        strip_b2c=True,
        extract_from_email=False,
        company_name_fallback=test_df["company_name"],
    )
    print(f"Cleaned website domains: {list(cleaned_website)}")
