# src/fmatch/preprocessing/rules/csv_utils.py
"""CSV utilities for global file format support."""

from __future__ import annotations

import csv
import logging
from pathlib import Path
from typing import Any, Optional, Union

import pandas as pd

log = logging.getLogger(__name__)

try:
    import chardet

    CHARDET_AVAILABLE = True
except ImportError:  # pragma: no cover
    chardet = None  # type: ignore
    CHARDET_AVAILABLE = False
    log.warning(
        "chardet not available; falling back to UTF-8 detection for CSV imports"
    )


def _sniff_delimiter(path: Path, sample_bytes: int = 32768) -> Optional[str]:
    """Quick delimiter detection to avoid slow Python engine."""
    try:
        with open(path, "rb") as f:
            chunk = f.read(sample_bytes)
        # Try UTF-8 first as it's most common
        text = chunk.decode("utf-8", errors="replace")
        # Use CSV sniffer to detect delimiter (limit to 8KB for speed)
        dialect = csv.Sniffer().sniff(text[:8192], delimiters=[",", ";", "|", "\t"])
        return dialect.delimiter
    except Exception:
        return None


def read_any_csv(file_path: Union[str, Path]) -> pd.DataFrame:
    """
    Read CSV files with automatic encoding detection and delimiter sniffing.

    Handles common European export formats:
    - UTF-8-SIG (Excel export with BOM)
    - Latin-1 (Windows Excel)
    - Semicolon delimiters
    - Auto-detects encoding for unknown files
    - Preserves leading zeros in phone/ID columns

    Args:
        file_path: Path to CSV file

    Returns:
        pandas DataFrame

    Raises:
        Exception: If file cannot be read with any encoding/delimiter
    """
    file_path = Path(file_path)

    # Columns that should be kept as strings to preserve leading zeros
    string_columns = [
        "phone",
        "mobilephone",
        "zip",
        "zipcode",
        "postalcode",
        "postcode",
        "externalid",
        "external_id",
        "vendorleadid",
        "vendor_lead_id",
        "customerid",
        "customer_id",
        "accountnumber",
        "account_number",
    ]

    # Try to sniff delimiter first for fast C engine
    delimiter = _sniff_delimiter(file_path)

    # Try common encodings first (fast path)
    encodings_to_try = [
        "utf-8-sig",  # Excel UTF-8 exports with BOM
        "utf-8",  # Standard UTF-8
        "latin-1",  # Windows Western European
        "cp1252",  # Windows Western European (superset of latin-1)
    ]

    for encoding in encodings_to_try:
        try:
            # Use sniffed delimiter for fast C engine when possible
            if delimiter:
                # First read to detect column names
                df_peek = pd.read_csv(
                    file_path,
                    sep=delimiter,
                    engine="c",  # Fast C engine
                    encoding=encoding,
                    nrows=0,  # Just get column names
                )

                # Build dtype dict for string columns (preserve leading zeros)
                dtype_dict = {}
                for col in df_peek.columns:
                    if col.lower().strip() in string_columns:
                        dtype_dict[col] = "str"

                # Now read full file with dtype protection
                return pd.read_csv(
                    file_path,
                    sep=delimiter,
                    engine="c",  # Fast C engine
                    encoding=encoding,
                    dtype=dtype_dict,  # Force string types for ID/phone columns
                    keep_default_na=False,  # Don't convert "00123" to NaN
                    on_bad_lines="skip",  # Skip malformed lines
                )
            else:
                # Fallback to auto-detect with Python engine
                df_peek = pd.read_csv(
                    file_path,
                    sep=None,  # Auto-detect delimiter
                    engine="python",
                    encoding=encoding,
                    nrows=0,  # Just get column names
                )

                dtype_dict = {}
                for col in df_peek.columns:
                    if col.lower().strip() in string_columns:
                        dtype_dict[col] = "str"

                return pd.read_csv(
                    file_path,
                    sep=None,  # Auto-detect delimiter
                    engine="python",  # Required for sep=None
                    encoding=encoding,
                    dtype=dtype_dict,
                    keep_default_na=False,
                )
        except (UnicodeDecodeError, UnicodeError):
            continue
        except Exception as e:
            # If it's not an encoding error, try the next encoding
            if "codec can't decode" in str(e).lower():
                continue
            # For other errors (like file not found), re-raise
            raise

    # If all common encodings failed, try auto-detection
    try:
        # Read a sample to detect encoding
        with open(file_path, "rb") as f:
            raw_data = f.read(10000)  # Read first 10KB for detection

        if CHARDET_AVAILABLE and chardet is not None:
            detected = chardet.detect(raw_data)
            detected_encoding = detected.get("encoding", "utf-8")
            confidence = detected.get("confidence", 0)
        else:
            detected_encoding = "utf-8"
            confidence = 0

        if confidence > 0.7:  # Only use if confident
            # Apply same dtype protection for detected encoding
            try:
                df_peek = pd.read_csv(
                    file_path,
                    sep=None,
                    engine="python",
                    encoding=detected_encoding,
                    nrows=0,
                )
                dtype_dict = {
                    col: "str"
                    for col in df_peek.columns
                    if col.lower().strip() in string_columns
                }

                return pd.read_csv(
                    file_path,
                    sep=None,
                    engine="python",
                    encoding=detected_encoding,
                    dtype=dtype_dict,
                    keep_default_na=False,
                    # low_memory not supported with python engine
                )
            except Exception:
                pass  # Fall through to last resort
    except Exception:
        pass

    # Last resort: try with error handling
    try:
        return pd.read_csv(
            file_path,
            sep=None,
            engine="python",
            encoding="utf-8",
            encoding_errors="replace",  # Replace bad characters
            # low_memory not supported with python engine
        )
    except Exception as e:
        raise Exception(f"Could not read CSV file {file_path} with any encoding: {e}")


def detect_csv_properties(file_path: Union[str, Path]) -> dict[str, Any]:
    """
    Analyze CSV file properties for debugging.

    Returns:
        Dictionary with encoding, delimiter, and sample data info
    """
    file_path = Path(file_path)

    # Detect encoding
    with open(file_path, "rb") as f:
        raw_data = f.read(10000)

    detected = chardet.detect(raw_data)

    # Try to read a sample
    try:
        sample_df = pd.read_csv(
            file_path, nrows=5, encoding=detected.get("encoding", "utf-8")
        )
        delimiter = ","
        if ";" in sample_df.columns[0] if len(sample_df.columns) == 1 else False:
            delimiter = ";"
    except Exception:
        sample_df = None
        delimiter = "unknown"

    return {
        "encoding": detected.get("encoding", "unknown"),
        "encoding_confidence": detected.get("confidence", 0),
        "delimiter": delimiter,
        "file_size": file_path.stat().st_size,
        "sample_columns": list(sample_df.columns) if sample_df is not None else None,
        "sample_rows": len(sample_df) if sample_df is not None else 0,
    }
