"""
Resolution prep: detect data type and suggest identifier resolution before enrichment.
"""

from __future__ import annotations

import os
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional

# Feature toggles (env-configurable)
RESOLUTION_PREP_ENABLED = os.getenv("FM_RESOLUTION_PREP_ENABLED", "true").lower() == "true"
RESOLUTION_PREP_MIN_ROWS = int(os.getenv("FM_RESOLUTION_PREP_MIN_ROWS", "3"))
RESOLUTION_PREP_CONTACT_CONFIDENCE = float(
    os.getenv("FM_RESOLUTION_PREP_CONTACT_CONFIDENCE", "0.7")
)


class DataType(Enum):
    COMPANY = "company"  # Account-level data (company names, domains)
    CONTACT = "contact"  # Person-level data (names, emails, titles)
    MIXED = "mixed"  # Has both company and contact columns
    UNKNOWN = "unknown"  # Can't determine


@dataclass
class ResolutionPrepRecommendation:
    data_type: DataType
    confidence: float  # 0-1 confidence in detection
    missing_identifiers: List[str]  # ["domain", "account_id", "contact_id"]
    suggested_steps: List[str]  # ["match_sfdc_contact", "match_sfdc_account", "company_to_domain"]
    has_identifiers: Dict[str, bool]  # {"email": True, "domain": False, ...}
    prep_needed: bool  # True if we should suggest prep
    user_message: str  # Human-readable suggestion
    column_mapping: Dict[str, str] = field(default_factory=dict)  # {"email": "Email Address", "company": "Company Name"}


# Column name patterns for detection
CONTACT_COLUMNS: Dict[str, List[str]] = {
    "email": ["email", "email_address", "e-mail", "emailaddress", "work_email", "personal_email"],
    "firstname": ["firstname", "first_name", "first", "fname", "given_name"],
    "lastname": ["lastname", "last_name", "last", "lname", "surname", "family_name"],
    "fullname": ["name", "full_name", "fullname", "contact_name", "person_name"],
    "title": ["title", "job_title", "jobtitle", "position", "role"],
    "phone": ["phone", "phone_number", "mobile", "cell", "telephone"],
}

COMPANY_COLUMNS: Dict[str, List[str]] = {
    "company": ["company", "company_name", "companyname", "organization", "org", "account", "account_name", "employer"],
    "domain": ["domain", "website", "url", "company_domain", "web"],
    "industry": ["industry", "sector", "vertical"],
    "revenue": ["revenue", "annual_revenue", "arr"],
    "employees": ["employees", "employee_count", "headcount", "size"],
}

IDENTIFIER_COLUMNS: Dict[str, List[str]] = {
    "account_id": ["account_id", "accountid", "sfdc_account_id", "salesforce_account_id", "sf_account_id"],
    "contact_id": ["contact_id", "contactid", "sfdc_contact_id", "salesforce_contact_id", "sf_contact_id"],
    "lead_id": ["lead_id", "leadid", "sfdc_lead_id"],
}

# Weights to help data type detection (email/domain are strong signals)
CONTACT_WEIGHTS: Dict[str, int] = {"email": 3, "fullname": 2, "firstname": 1, "lastname": 1, "title": 1, "phone": 1}
COMPANY_WEIGHTS: Dict[str, int] = {"company": 3, "domain": 3, "industry": 1, "revenue": 1, "employees": 1}
IDENTIFIER_WEIGHTS: Dict[str, int] = {"account_id": 2, "contact_id": 3, "lead_id": 2}


def _normalize_column(name: str) -> str:
    """Normalize column names for fuzzy matching."""
    return "".join(ch for ch in name.lower().strip() if ch.isalnum())


def _convert_sample_rows(
    columns: List[str],
    sample_rows: Optional[List[Any]],
) -> Optional[List[Dict[str, Any]]]:
    """
    Normalize sample rows into dicts keyed by column headers.

    Accepts list of dicts (pass-through) or list/tuple rows aligned to headers.
    """
    if not sample_rows:
        return None

    if sample_rows and isinstance(sample_rows[0], dict):
        return sample_rows  # type: ignore[return-value]

    converted: List[Dict[str, Any]] = []
    for row in sample_rows:
        if not isinstance(row, (list, tuple)):
            continue
        converted.append(
            {
                columns[idx]: row[idx]
                for idx in range(min(len(columns), len(row)))
                if idx < len(row)
            }
        )
    return converted or None


def _column_is_populated(column: Optional[str], rows: Optional[List[Dict[str, Any]]]) -> bool:
    """Check whether a column has at least one non-empty value in sample rows."""
    if not column:
        return False
    if not rows:
        return True  # Assume present if we lack visibility into row values

    for row in rows:
        value = row.get(column)
        if value is None:
            continue
        if isinstance(value, str):
            value = value.strip()
        if value:
            return True
    return False


def _match_columns(columns: List[str], patterns: Dict[str, List[str]]) -> Dict[str, str]:
    """Return mapping of canonical key -> actual column name based on pattern matches."""
    normalized = { _normalize_column(col): col for col in columns if col }
    mapping: Dict[str, str] = {}

    for key, variants in patterns.items():
        for variant in variants:
            norm_variant = _normalize_column(variant)
            if norm_variant in normalized:
                mapping[key] = normalized[norm_variant]
                break
    return mapping


def _score_matches(mapping: Dict[str, str], weights: Dict[str, int]) -> int:
    """Compute weighted score from matched columns."""
    score = 0
    for key in mapping:
        score += weights.get(key, 1)
    return score


def _build_suggested_steps(
    data_type: DataType,
    has_identifiers: Dict[str, bool],
    column_mapping: Dict[str, str],
) -> List[str]:
    """
    Build ordered list of resolution steps based on data type and missing identifiers.

    For CONTACT data:
    1. match_sfdc_contact (by email) -> returns contact_id, account_id
    2. match_sfdc_account (for contacts without account) -> returns account_id
    3. company_to_domain (for remaining) -> returns domain

    For COMPANY data:
    1. match_sfdc_account (by domain or name) -> returns account_id
    2. company_to_domain (for unmatched) -> returns domain

    For MIXED data:
    1. match_sfdc_contact (if has email/name columns)
    2. match_sfdc_account
    3. company_to_domain
    """
    steps: List[str] = []

    if data_type == DataType.CONTACT:
        # Contact flow: email match first, then account resolution
        if not has_identifiers.get("contact_id"):
            if column_mapping.get("email"):
                steps.append("match_sfdc_contact")
        if not has_identifiers.get("account_id"):
            steps.append("match_sfdc_account")
        if not has_identifiers.get("domain"):
            steps.append("company_to_domain")

    elif data_type == DataType.COMPANY:
        # Company flow: account match, then domain resolution
        if not has_identifiers.get("account_id"):
            steps.append("match_sfdc_account")
        if not has_identifiers.get("domain"):
            steps.append("company_to_domain")

    elif data_type == DataType.MIXED:
        # Mixed: run both flows
        if not has_identifiers.get("contact_id") and column_mapping.get("email"):
            steps.append("match_sfdc_contact")
        if not has_identifiers.get("account_id"):
            steps.append("match_sfdc_account")
        if not has_identifiers.get("domain"):
            steps.append("company_to_domain")

    return steps


def _build_user_message(
    data_type: DataType,
    suggested_steps: List[str],
    column_mapping: Dict[str, str],
) -> str:
    """Generate human-readable suggestion for the user."""

    if not suggested_steps:
        return ""

    if data_type == DataType.CONTACT:
        msg = "I notice this looks like contact data"
        if column_mapping.get("email"):
            msg += f" (found email column: {column_mapping['email']})"
        msg += ".\n\nFor better enrichment results, I can:\n"

        step_descriptions = []
        for i, step in enumerate(suggested_steps, 1):
            if step == "match_sfdc_contact":
                step_descriptions.append(f"{i}. Match contacts to Salesforce by email")
            elif step == "match_sfdc_account":
                step_descriptions.append(f"{i}. Link unmatched contacts to Salesforce Accounts")
            elif step == "company_to_domain":
                step_descriptions.append(f"{i}. Resolve company domains for remaining rows")

        msg += "\n".join(step_descriptions)
        msg += "\n\nThis typically improves match rates by 40-60%. Proceed?"

    elif data_type == DataType.COMPANY:
        msg = "I notice your data has company names but "
        missing_parts: List[str] = []
        if "match_sfdc_account" in suggested_steps:
            missing_parts.append("no Salesforce Account IDs")
        if "company_to_domain" in suggested_steps:
            missing_parts.append("no domains")
        msg += " and ".join(missing_parts) + ".\n\nFor better accuracy, I can:\n"

        step_descriptions = []
        for i, step in enumerate(suggested_steps, 1):
            if step == "match_sfdc_account":
                step_descriptions.append(f"{i}. Match companies to your Salesforce CRM")
            elif step == "company_to_domain":
                step_descriptions.append(f"{i}. Resolve domains for unmatched companies")

        msg += "\n".join(step_descriptions)
        msg += "\n\nProceed with these steps first?"

    else:  # MIXED
        msg = "This data appears to have both contact and company information.\n\n"
        msg += "For optimal results, I can run identifier resolution first:\n"
        step_descriptions = []
        for i, step in enumerate(suggested_steps, 1):
            if step == "match_sfdc_contact":
                step_descriptions.append(f"{i}. Match contacts to Salesforce by email")
            elif step == "match_sfdc_account":
                step_descriptions.append(f"{i}. Link rows to Salesforce Accounts")
            elif step == "company_to_domain":
                step_descriptions.append(f"{i}. Resolve company domains for remaining rows")
        msg += "\n".join(step_descriptions)
        msg += "\n\nThis usually boosts match rates. Proceed?"

    return msg


def detect_data_type(columns: List[str], sample_rows: Optional[List[Dict[str, Any]]] = None) -> ResolutionPrepRecommendation:
    """
    Analyze columns and sample data to determine data type and missing identifiers.

    Returns recommendation with:
    - data_type: COMPANY, CONTACT, MIXED, or UNKNOWN
    - missing_identifiers: what's needed for optimal resolution
    - suggested_steps: ordered list of prep operations
    - user_message: what to show the user
    """
    # Early exit if disabled
    if not RESOLUTION_PREP_ENABLED:
        return ResolutionPrepRecommendation(
            data_type=DataType.UNKNOWN,
            confidence=0.0,
            missing_identifiers=[],
            suggested_steps=[],
            has_identifiers={},
            prep_needed=False,
            user_message="",
            column_mapping={},
        )

    columns = [col for col in columns or [] if col]
    normalized_samples = _convert_sample_rows(columns, sample_rows)

    contact_mapping = _match_columns(columns, CONTACT_COLUMNS)
    company_mapping = _match_columns(columns, COMPANY_COLUMNS)
    identifier_mapping = _match_columns(columns, IDENTIFIER_COLUMNS)

    # Consolidated mapping (canonical -> actual header)
    column_mapping: Dict[str, str] = {}
    column_mapping.update(contact_mapping)
    column_mapping.update(company_mapping)
    column_mapping.update(identifier_mapping)

    contact_score = _score_matches(contact_mapping, CONTACT_WEIGHTS)
    company_score = _score_matches(company_mapping, COMPANY_WEIGHTS)

    # Identifiers can also inform the data type slightly
    if "contact_id" in identifier_mapping:
        contact_score += IDENTIFIER_WEIGHTS.get("contact_id", 0)
    if "lead_id" in identifier_mapping:
        contact_score += IDENTIFIER_WEIGHTS.get("lead_id", 0)
    if "account_id" in identifier_mapping:
        company_score += IDENTIFIER_WEIGHTS.get("account_id", 0)

    total_score = contact_score + company_score
    contact_ratio = contact_score / total_score if total_score else 0.0
    company_ratio = company_score / total_score if total_score else 0.0

    if total_score == 0:
        data_type = DataType.UNKNOWN
        confidence = 0.0
    elif contact_score and not company_score:
        data_type = DataType.CONTACT
        confidence = min(1.0, max(contact_ratio, 0.8))
    elif company_score and not contact_score:
        data_type = DataType.COMPANY
        confidence = min(1.0, max(company_ratio, 0.8))
    else:
        # Both present
        if contact_ratio >= RESOLUTION_PREP_CONTACT_CONFIDENCE and contact_ratio >= company_ratio:
            data_type = DataType.CONTACT
        elif company_ratio >= 0.65 and company_ratio > contact_ratio:
            data_type = DataType.COMPANY
        else:
            data_type = DataType.MIXED
        confidence = max(contact_ratio, company_ratio)

    # Determine identifier availability (presence + populated rows when available)
    has_identifiers: Dict[str, bool] = {
        "email": _column_is_populated(column_mapping.get("email"), normalized_samples),
        "domain": _column_is_populated(column_mapping.get("domain"), normalized_samples),
        "account_id": _column_is_populated(column_mapping.get("account_id"), normalized_samples),
        "contact_id": _column_is_populated(column_mapping.get("contact_id"), normalized_samples),
        "lead_id": _column_is_populated(column_mapping.get("lead_id"), normalized_samples),
        "company": _column_is_populated(column_mapping.get("company"), normalized_samples),
    }

    missing_identifiers: List[str] = []
    if data_type in (DataType.CONTACT, DataType.MIXED):
        if not has_identifiers.get("contact_id"):
            missing_identifiers.append("contact_id")
        if not has_identifiers.get("account_id"):
            missing_identifiers.append("account_id")
        if not has_identifiers.get("domain"):
            missing_identifiers.append("domain")
    elif data_type == DataType.COMPANY:
        if not has_identifiers.get("account_id"):
            missing_identifiers.append("account_id")
        if not has_identifiers.get("domain"):
            missing_identifiers.append("domain")

    suggested_steps = _build_suggested_steps(data_type, has_identifiers, column_mapping)
    user_message = _build_user_message(data_type, suggested_steps, column_mapping)

    # Gate on minimum rows; fall back to min threshold if sample size unknown
    row_estimate = len(normalized_samples or []) if normalized_samples is not None else RESOLUTION_PREP_MIN_ROWS
    prep_needed = (
        data_type != DataType.UNKNOWN
        and bool(missing_identifiers)
        and bool(suggested_steps)
        and row_estimate >= RESOLUTION_PREP_MIN_ROWS
    )

    return ResolutionPrepRecommendation(
        data_type=data_type,
        confidence=round(confidence, 3),
        missing_identifiers=missing_identifiers,
        suggested_steps=suggested_steps,
        has_identifiers=has_identifiers,
        prep_needed=prep_needed,
        user_message=user_message,
        column_mapping=column_mapping,
    )
