"""Column-name-based FK inference — works without sample data.

Infers foreign-key relationships by matching column entity tokens against
table identity tokens.  For example:

    FACT_FINANCIAL_ACTUALS.ACCOUNT_ID  →  DIM_ACCOUNT.ACCOUNT_ID
    FACT_FINANCIAL_ACTUALS.CORP_HID    →  DIM_CORP.CORP_HID

Algorithm:
    1. Build table identity map: strip DIM_/FACT_ prefix → entity token.
    2. For each table's columns ending in a key suffix, extract entity token.
    3. Match entity token against table identity map → FK candidate.
    4. Score: +3 exact column match, +2 entity→table match, +1 DIM_ target.
    5. Find best target PK column: prefer exact name match > same entity.
"""

from __future__ import annotations

import logging
import re
from typing import Any, Dict, List, Optional, Tuple

logger = logging.getLogger(__name__)

from .focus_config import InferenceConfig


# Default warehouse prefixes to strip from table names
_WAREHOUSE_PREFIXES = ("dim_", "fact_", "xref_", "bridge_", "stg_", "raw_")

# Default key suffixes when no config provided
_DEFAULT_KEY_SUFFIXES = ["_id", "_code", "_num", "tid", "hid", "_key"]


def _strip_table_prefix(name: str) -> str:
    """Strip warehouse prefixes (DIM_, FACT_, etc.) from a table name."""
    lower = name.lower()
    for pfx in _WAREHOUSE_PREFIXES:
        if lower.startswith(pfx):
            return name[len(pfx):]
    return name


def _entity_token(name: str) -> str:
    """Normalise a name to a lowercase, underscore-free entity token.

    ``COST_CENTER`` → ``costcenter``, ``ACCOUNT`` → ``account``.
    """
    return re.sub(r"[^a-z0-9]", "", name.lower())


def _extract_column_entity(
    col: str,
    key_suffixes: List[str],
) -> Optional[str]:
    """Extract the entity token from a column name by stripping key suffixes.

    ``ACCOUNT_ID`` → ``account``, ``COST_CENTER_HID`` → ``costcenter``.
    Returns None if no suffix matches.
    """
    col_lower = col.lower()
    for suffix in sorted(key_suffixes, key=len, reverse=True):
        if col_lower.endswith(suffix):
            entity_part = col[:len(col) - len(suffix)]
            if entity_part:
                # Strip leading underscore if present
                entity_part = entity_part.strip("_")
                return _entity_token(entity_part)
    return None


def _build_table_identity_map(
    table_names: List[str],
) -> Dict[str, List[str]]:
    """Map entity tokens → list of table names.

    ``DIM_ACCOUNT`` → token ``account`` → [``DIM_ACCOUNT``].
    ``DIM_COST_CENTER`` → token ``costcenter`` → [``DIM_COST_CENTER``].
    """
    identity: Dict[str, List[str]] = {}
    for table in table_names:
        stripped = _strip_table_prefix(table)
        token = _entity_token(stripped)
        if token:
            identity.setdefault(token, []).append(table)
    return identity


def _find_best_pk_column(
    target_columns: List[str],
    source_column: str,
    entity_token: str,
    key_suffixes: List[str],
) -> Optional[str]:
    """Find the best PK column in the target table for a given FK column.

    Priority:
    1. Exact column name match (source has ACCOUNT_ID, target has ACCOUNT_ID).
    2. Same entity token + key suffix in target.
    3. Generic ID-like column in target (just 'ID').
    """
    src_lower = source_column.lower()

    # Priority 1: exact name match
    for col in target_columns:
        if col.lower() == src_lower:
            return col

    # Priority 2: same entity + any key suffix
    for col in target_columns:
        col_entity = _extract_column_entity(col, key_suffixes)
        if col_entity == entity_token:
            return col

    # Priority 3: column named just 'ID'
    for col in target_columns:
        if col.lower() == "id":
            return col

    return None


def _score_match(
    source_column: str,
    target_table: str,
    target_pk: str,
    entity_token: str,
) -> int:
    """Score a candidate FK relationship.

    +3: exact column name match between source FK and target PK.
    +2: entity token matches table identity.
    +1: target table starts with DIM_ (likely dimension).
    """
    score = 0

    # Exact column name match
    if source_column.lower() == target_pk.lower():
        score += 3

    # Entity→table match (always true if we got here via token matching)
    score += 2

    # DIM_ prefix bonus
    if target_table.upper().startswith("DIM_"):
        score += 1

    return score


def infer_relationships_by_name(
    table_columns: Dict[str, List[str]],
    config: Optional[InferenceConfig] = None,
    min_score: int = 2,
    ai_advisor: Optional[Any] = None,
) -> List[dict]:
    """Infer FK relationships from column and table names alone.

    No sample data is required — pure structural/naming analysis.

    Args:
        table_columns: Dict of table_name → list of column names.
        config: InferenceConfig for key_suffixes. Uses defaults if None.
        min_score: Minimum score to include a relationship (default 2).

    Returns:
        List of relationship dicts compatible with ``infer_relationships()``
        output format.
    """
    key_suffixes = (config.key_suffixes if config else _DEFAULT_KEY_SUFFIXES)
    exclude_columns = set(config.exclude_columns if config else [])

    table_names = list(table_columns.keys())
    identity_map = _build_table_identity_map(table_names)

    relationships: List[dict] = []
    seen: set = set()  # (source_table, source_column, target_table)

    for src_table, src_cols in table_columns.items():
        for src_col in src_cols:
            if src_col in exclude_columns:
                continue

            entity = _extract_column_entity(src_col, key_suffixes)
            if entity is None:
                continue

            # Skip generic "id" columns (just "ID" alone, no entity prefix)
            if not entity:
                continue

            # Find candidate target tables via identity map
            candidate_tables = identity_map.get(entity, [])
            if not candidate_tables:
                # Try partial match: entity token is a substring of table token
                for token, tables in identity_map.items():
                    if entity in token or token in entity:
                        candidate_tables.extend(tables)

            for tgt_table in candidate_tables:
                # Skip self-references
                if tgt_table == src_table:
                    continue

                dedup_key = (src_table, src_col, tgt_table)
                if dedup_key in seen:
                    continue

                tgt_cols = table_columns.get(tgt_table, [])
                tgt_pk = _find_best_pk_column(tgt_cols, src_col, entity, key_suffixes)
                if tgt_pk is None:
                    continue

                score = _score_match(src_col, tgt_table, tgt_pk, entity)
                if score < min_score:
                    continue

                seen.add(dedup_key)
                relationships.append({
                    "source_table": src_table,
                    "source_column": src_col,
                    "target_table": tgt_table,
                    "target_column": tgt_pk,
                    "overlap": 0.0,
                    "relationship": "foreign_key",
                    "confidence": "high" if score >= 5 else "medium",
                    "method": "column_name",
                    "score": score,
                })

    # AI enrichment (additive only)
    if ai_advisor and ai_advisor.available:
        try:
            enrichment = ai_advisor.enhance_relationship_inference(
                inferred_rels=relationships,
                table_columns=table_columns,
            )
            if enrichment:
                # Merge AI-discovered semantic relationships into output
                for sem_rel in enrichment.get("semantic_relationships", []):
                    sem_rel["source"] = "ai_advisor"
                    sem_rel.setdefault("overlap", 0.0)
                    sem_rel.setdefault("relationship", "foreign_key")
                    sem_rel.setdefault("confidence", "medium")
                    sem_rel.setdefault("method", "ai_semantic")
                    relationships.append(sem_rel)
                # Attach full enrichment as metadata entry
                relationships.append({"_ai_advisor": enrichment})
        except Exception:
            logger.debug("AI advisor enrichment failed for infer_relationships_by_name", exc_info=True)

    return relationships
