"""
CSV field mapping for sequencer integrations.

Provides flexible mapping from CSV columns to Prospect fields,
with sensible defaults that auto-detect common column names.
"""

from __future__ import annotations

import csv
import hashlib
from pathlib import Path
from typing import Any, Dict, List, Optional

import yaml

from g_gremlin.sequencer import SequencerValidationError
from g_gremlin.sequencer.models import Prospect


# Default column name mappings (case-insensitive matching)
DEFAULT_FIELD_MAPPINGS: Dict[str, List[str]] = {
    "email": ["email", "Email", "EMAIL", "email_address", "e-mail", "E-mail"],
    "first_name": ["first_name", "firstName", "First Name", "first", "First", "given_name"],
    "last_name": ["last_name", "lastName", "Last Name", "last", "Last", "surname", "family_name"],
    "company": ["company", "Company", "company_name", "organization", "Organisation", "org"],
    "title": ["title", "Title", "job_title", "Job Title", "position", "Position", "role"],
    "phone": ["phone", "Phone", "phone_number", "mobile", "Mobile", "telephone"],
    "linkedin_url": ["linkedin", "LinkedIn", "linkedin_url", "linkedIn_url", "li_url"],
    "domain": ["domain", "Domain", "website", "Website", "company_domain", "url"],
}

# SFDC ID field mappings
SFDC_FIELD_MAPPINGS: Dict[str, List[str]] = {
    "sfdc_contact_id": ["sfdc_contact_id", "ContactId", "contact_id", "salesforce_contact_id"],
    "sfdc_account_id": ["sfdc_account_id", "AccountId", "account_id", "salesforce_account_id"],
    "sfdc_lead_id": ["sfdc_lead_id", "LeadId", "lead_id", "salesforce_lead_id"],
}


def _find_column(headers: List[str], candidates: List[str]) -> Optional[str]:
    """Find the first matching column header from candidates list."""
    header_lower = {h.lower(): h for h in headers}
    for candidate in candidates:
        if candidate.lower() in header_lower:
            return header_lower[candidate.lower()]
    return None


def _build_column_mapping(
    headers: List[str],
    custom_mapping: Optional[Dict[str, str]] = None,
) -> Dict[str, Optional[str]]:
    """
    Build column mapping from headers using defaults + custom overrides.

    Args:
        headers: CSV column headers
        custom_mapping: Optional custom field -> column mapping

    Returns:
        Dict mapping field names to column names (or None if not found)
    """
    result: Dict[str, Optional[str]] = {}

    # Apply default mappings
    for field, candidates in DEFAULT_FIELD_MAPPINGS.items():
        result[field] = _find_column(headers, candidates)

    # Apply SFDC mappings
    for field, candidates in SFDC_FIELD_MAPPINGS.items():
        result[field] = _find_column(headers, candidates)

    # Apply custom overrides
    if custom_mapping:
        for field, column in custom_mapping.items():
            if field != "custom_vars" and column in headers:
                result[field] = column

    return result


def load_mapping_file(path: Path) -> Dict[str, Any]:
    """
    Load a custom mapping file (YAML format).

    Example mapping.yaml:
        email: "contact_email"
        first_name: "given_name"
        custom_vars:
            industry: "vertical"
            revenue: "arr_bucket"
    """
    with open(path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f) or {}


def parse_csv(
    csv_path: Path,
    mapping_path: Optional[Path] = None,
) -> List[Prospect]:
    """
    Parse CSV file into list of Prospect objects.

    Args:
        csv_path: Path to CSV file
        mapping_path: Optional path to custom mapping YAML

    Returns:
        List of Prospect objects

    Raises:
        SequencerValidationError: If email column is missing or CSV is empty
    """
    custom_mapping = None
    custom_vars_mapping: Dict[str, str] = {}

    if mapping_path:
        mapping_config = load_mapping_file(mapping_path)
        # Extract custom_vars mapping separately
        custom_vars_mapping = mapping_config.pop("custom_vars", {})
        custom_mapping = mapping_config

    # Read CSV
    with open(csv_path, newline="", encoding="utf-8-sig") as f:
        reader = list(csv.DictReader(f))

    if not reader:
        raise SequencerValidationError(f"CSV file is empty: {csv_path}")

    headers = list(reader[0].keys())
    column_map = _build_column_mapping(headers, custom_mapping)

    # Validate email column exists
    if not column_map.get("email"):
        raise SequencerValidationError(
            f"Required 'email' column not found in CSV. "
            f"Available columns: {', '.join(headers)}"
        )

    prospects: List[Prospect] = []

    for row in reader:
        email = row.get(column_map["email"], "").strip()
        if not email:
            continue  # Skip rows without email

        # Build custom_vars from explicit mapping + any extra columns
        custom_vars: Dict[str, Any] = {}

        # Apply custom_vars mapping from config
        for var_name, column_name in custom_vars_mapping.items():
            if column_name in row and row[column_name]:
                custom_vars[var_name] = row[column_name]

        # Also capture any columns not in standard mapping as custom_vars
        mapped_columns = set(c for c in column_map.values() if c)
        for col, value in row.items():
            if col not in mapped_columns and value and col not in custom_vars_mapping.values():
                # Normalize column name for custom var key
                var_key = col.lower().replace(" ", "_").replace("-", "_")
                custom_vars[var_key] = value

        prospect = Prospect(
            email=email,
            first_name=_get_value(row, column_map, "first_name"),
            last_name=_get_value(row, column_map, "last_name"),
            company=_get_value(row, column_map, "company"),
            title=_get_value(row, column_map, "title"),
            phone=_get_value(row, column_map, "phone"),
            linkedin_url=_get_value(row, column_map, "linkedin_url"),
            domain=_get_value(row, column_map, "domain"),
            sfdc_contact_id=_get_value(row, column_map, "sfdc_contact_id"),
            sfdc_account_id=_get_value(row, column_map, "sfdc_account_id"),
            sfdc_lead_id=_get_value(row, column_map, "sfdc_lead_id"),
            custom_vars=custom_vars if custom_vars else {},
        )
        prospects.append(prospect)

    if not prospects:
        raise SequencerValidationError(
            f"No valid prospects found in CSV (all rows missing email): {csv_path}"
        )

    return prospects


def _get_value(
    row: Dict[str, str],
    column_map: Dict[str, Optional[str]],
    field: str,
) -> Optional[str]:
    """Get value from row using column mapping."""
    column = column_map.get(field)
    if column and column in row:
        value = row[column].strip()
        return value if value else None
    return None


def compute_plan_hash(
    csv_path: Path,
    campaign_id: str,
    provider: str,
    profile: str = "default",
) -> str:
    """
    Compute a deterministic hash for idempotency.

    Hash is based on:
    - CSV file content (SHA256)
    - Campaign ID
    - Provider name
    - Profile name

    Returns:
        Hex digest of SHA256 hash
    """
    hasher = hashlib.sha256()

    # Hash file content
    with open(csv_path, "rb") as f:
        hasher.update(f.read())

    # Hash campaign, provider, profile
    hasher.update(campaign_id.encode("utf-8"))
    hasher.update(provider.encode("utf-8"))
    hasher.update(profile.encode("utf-8"))

    return hasher.hexdigest()


def chunk_list(items: List[Any], size: int) -> List[List[Any]]:
    """Split a list into chunks of specified size."""
    return [items[i : i + size] for i in range(0, len(items), size)]


def validate_prospects(prospects: List[Prospect]) -> List[Dict[str, str]]:
    """
    Validate prospects and return list of validation errors.

    Returns:
        List of error dicts: [{"email": "...", "error": "..."}]
    """
    errors: List[Dict[str, str]] = []

    for prospect in prospects:
        # Basic email validation
        if not prospect.email or "@" not in prospect.email:
            errors.append({
                "email": prospect.email or "(empty)",
                "error": "Invalid email format",
            })

    return errors
