"""Infer table relationships from samples."""
from __future__ import annotations

import logging
from typing import Dict, List, Optional

logger = logging.getLogger(__name__)

from .focus_config import InferenceConfig, FocusConfig

# Module-level default config (can be replaced via model_configure_inference)
_default_config = InferenceConfig()


def set_default_config(config: InferenceConfig) -> None:
    """Replace the module-level default inference config."""
    global _default_config
    _default_config = config


def get_default_config() -> InferenceConfig:
    """Return the current module-level inference config."""
    return _default_config


def _column_values(rows: List[dict], column: str) -> set:
    return {r.get(column) for r in rows if r.get(column) is not None}


def _is_unique(values: List) -> bool:
    non_null = [v for v in values if v is not None]
    return len(non_null) == len(set(non_null)) and len(non_null) > 0


def _normalize(name: str) -> str:
    return "".join(c for c in name.lower() if c.isalnum())


_WAREHOUSE_PREFIXES = ["dim_", "fact_", "xref_", "bridge_", "stg_", "raw_"]


def _table_keys(table: str, config: Optional[InferenceConfig] = None) -> List[str]:
    """Tokenize a table name for FK matching.

    Strips warehouse prefixes (DIM_, FACT_, etc.) and ERP prefixes before
    normalizing so that ``DIM_ACCOUNT`` produces ``{account, dimaccount}``.
    """
    name = table.lower()
    stripped = name
    # Strip warehouse prefixes
    for pfx in _WAREHOUSE_PREFIXES:
        if stripped.startswith(pfx):
            stripped = stripped[len(pfx):]
            break
    # Strip ERP prefixes from config
    if config:
        for pfx in config.strip_prefixes:
            if stripped.startswith(pfx):
                stripped = stripped[len(pfx):]
                break
    base = _normalize(name)
    core = _normalize(stripped)
    keys = {base, core}
    # Add plural-stripped variants
    if core.endswith("es") and len(core) > 2:
        keys.add(core[:-2])
    if core.endswith("s") and len(core) > 1:
        keys.add(core[:-1])
    return [k for k in keys if k]


def _col_tokens(col: str, config: InferenceConfig) -> List[str]:
    name = col.lower()
    for suffix in config.key_suffixes:
        if name.endswith(suffix):
            name = name[: -len(suffix)]
            break
    # remove common prefixes
    for prefix in config.strip_prefixes:
        if name.startswith(prefix):
            name = name[len(prefix):]
    name = _normalize(name)
    return [name] if name else []


def _is_focus_table(table: str, focus: FocusConfig) -> bool:
    """Check if a table matches transaction-focus patterns."""
    upper = table.upper()
    return any(pat in upper for pat in focus.patterns)


def _infer_by_value_overlap(
    samples: Dict[str, Dict[str, List[dict]]],
    min_overlap: float,
    cfg: InferenceConfig,
    focus_config: Optional[FocusConfig] = None,
) -> List[dict]:
    """Original value-overlap FK inference (internal helper)."""
    effective_overlap = min_overlap
    relationships: List[dict] = []

    table_cols = {
        t: data.get("columns", [])
        for t, data in samples.items()
    }

    logger.info("Inferring relationships: %d tables, min_overlap=%.2f", len(table_cols), effective_overlap)

    pk_candidates: Dict[str, List[str]] = {}
    for table, data in samples.items():
        rows = data.get("rows", [])
        cols = data.get("columns", [])
        pk_cols = []
        for col in cols:
            if col in cfg.exclude_columns:
                continue
            values = [r.get(col) for r in rows]
            col_lower = col.lower()
            if col_lower == "id" or any(col_lower.endswith(s) for s in cfg.key_suffixes):
                if _is_unique(values):
                    pk_cols.append(col)
        pk_candidates[table] = pk_cols

    processed = 0
    for src_table, src_cols in table_cols.items():
        processed += 1
        if processed % 20 == 0 or processed == len(table_cols):
            logger.info("  Relationship scan: %d/%d source tables", processed, len(table_cols))

        src_overlap = effective_overlap
        if focus_config and _is_focus_table(src_table, focus_config):
            src_overlap = focus_config.overlap_threshold

        for tgt_table, tgt_cols in table_cols.items():
            if src_table == tgt_table:
                continue
            tgt_pk_cols = pk_candidates.get(tgt_table, [])
            if not tgt_pk_cols:
                continue
            tgt_keys = _table_keys(tgt_table, config=cfg)
            for src_col in src_cols:
                if src_col in cfg.exclude_columns:
                    continue
                if not any(src_col.lower().endswith(s) for s in cfg.key_suffixes):
                    continue
                src_vals = _column_values(samples[src_table]["rows"], src_col)
                if not src_vals:
                    continue
                name_tokens = _col_tokens(src_col, cfg)
                name_match = any(tok in tgt_keys for tok in name_tokens)
                for tgt_col in tgt_pk_cols:
                    tgt_vals = _column_values(samples[tgt_table]["rows"], tgt_col)
                    if not tgt_vals:
                        continue
                    overlap = len(src_vals & tgt_vals) / max(len(src_vals), 1)
                    if overlap >= src_overlap or (name_match and overlap >= 0.05):
                        relationships.append({
                            "source_table": src_table,
                            "source_column": src_col,
                            "target_table": tgt_table,
                            "target_column": tgt_col,
                            "overlap": round(overlap, 3),
                            "relationship": "foreign_key",
                            "confidence": "high" if overlap >= effective_overlap else "medium",
                            "method": "value_overlap",
                        })

    return relationships


def _merge_relationships(
    overlap_rels: List[dict],
    name_rels: List[dict],
) -> List[dict]:
    """Merge value-overlap and column-name relationships, deduplicating.

    When both strategies find the same FK (same source_table, source_column,
    target_table), prefer the value-overlap result since it has empirical
    evidence.
    """
    seen: set = set()
    merged: List[dict] = []

    # Overlap results take priority
    for rel in overlap_rels:
        key = (rel["source_table"], rel["source_column"], rel["target_table"])
        if key not in seen:
            seen.add(key)
            merged.append(rel)

    # Add name-based results that weren't found by overlap
    for rel in name_rels:
        key = (rel["source_table"], rel["source_column"], rel["target_table"])
        if key not in seen:
            seen.add(key)
            merged.append(rel)

    return merged


def infer_relationships(
    samples: Dict[str, Dict[str, List[dict]]],
    min_overlap: float = 0.5,
    config: Optional[InferenceConfig] = None,
    focus_config: Optional[FocusConfig] = None,
) -> List[dict]:
    """Infer FK relationships from table samples.

    Combines two strategies:
    1. **Value overlap** — compares sample values between FK and PK columns.
    2. **Column name** — matches column entity tokens to table identity tokens.

    Results are merged with overlap taking priority for duplicates.

    Args:
        samples: Dict of table_name -> {"columns": [...], "rows": [...]}.
        min_overlap: Minimum value overlap ratio for FK match.
        config: Inference config for suffixes/prefixes. Uses module default if None.
        focus_config: Optional transaction-focus overrides for TXN/TRANS tables.

    Returns:
        List of relationship dicts.
    """
    cfg = config or _default_config
    effective_overlap = min_overlap if min_overlap != 0.5 else cfg.min_overlap

    # Strategy 1: value overlap (original)
    overlap_rels = _infer_by_value_overlap(
        samples, effective_overlap, cfg, focus_config,
    )

    # Strategy 2: column-name matching (no sample data needed)
    from .column_fk_inferer import infer_relationships_by_name

    table_columns = {t: data.get("columns", []) for t, data in samples.items()}
    name_rels = infer_relationships_by_name(table_columns, config=cfg)

    return _merge_relationships(overlap_rels, name_rels)
