from typing import Dict, List, Any, Optional
from pydantic import BaseModel
import hashlib
import re
from datetime import datetime

try:
    from deep_translator import GoogleTranslator
except ImportError:
    GoogleTranslator = None


class FieldDefinition(BaseModel):
    id: str  # Hash of name + type
    name: str  # Original column name
    type: str  # Original SQL type string
    origin: str  # "schema.table" where first seen
    usage: List[str] = []  # List of "schema.table" where seen
    is_auto_translated: bool = False
    is_manually_translated: bool = False
    aliases: Dict[str, str] = {}  # "en": "User ID", "es": "Usuario ID", "default": ...
    context_overrides: Dict[str, Dict[str, str]] = (
        {}
    )  # {"orders_table": {"en": "Order Status"}}
    mappings: Dict[str, str] = {}  # "pyarrow": "int64", "clickhouse": "UInt64"
    target_column: Optional[str] = None  # Persisted target attribute name
    ordinals: Dict[str, int] = {}  # "schema.table" -> 1 (Database Position)

    def update_usage(self, location: str):
        if location not in self.usage:
            self.usage.append(location)


class Sanitizer:
    """
    Prepares field names for translation or NLP.
    Handles CamelCase splitting, separator replacement, and noise reduction.
    """

    @staticmethod
    def sanitize(name: str) -> str:
        # 1. Split CamelCase (e.g. fechaInicio -> fecha Inicio)
        # Regex looks for lowercase followed by uppercase
        s1 = re.sub("(.)([A-Z][a-z]+)", r"\1 \2", name)
        s2 = re.sub("([a-z0-9])([A-Z])", r"\1 \2", s1)

        # 2. Replace separators with spaces
        s3 = re.sub(r"[_\-\s]+", " ", s2)

        # 3. Clean and Title Case
        return s3.strip().title()


class TypeMapper:
    """Helper to map SQL types to other systems"""

    @staticmethod
    def map_to_pyarrow(sql_type: str) -> str:
        sql_type = sql_type.upper()
        if "INT" in sql_type:
            return "int64"
        if "CHAR" in sql_type or "TEXT" in sql_type:
            return "string"
        if "FLOAT" in sql_type or "DOUBLE" in sql_type:
            return "float64"
        if "BOOL" in sql_type:
            return "bool"
        if "DATE" in sql_type or "TIME" in sql_type:
            return "timestamp[ns]"
        return "string"  # fallback

    @staticmethod
    def map_to_clickhouse(sql_type: str) -> str:
        sql_type = sql_type.upper()
        if "BIGINT" in sql_type:
            return "Int64"
        if "INT" in sql_type:
            return "Int32"
        if "CHAR" in sql_type or "TEXT" in sql_type:
            return "String"
        if "FLOAT" in sql_type:
            return "Float32"
        if "DOUBLE" in sql_type:
            return "Float64"
        if "BOOL" in sql_type:
            return "Bool"
        if "DATE" in sql_type:
            return "Date32"
        if "TIMESTAMP" in sql_type:
            return "DateTime64"
        return "String"


class FieldTranslationManager:
    def __init__(self):
        self.fields: Dict[str, FieldDefinition] = {}  # id -> FieldDefinition

    def _generate_id(self, name: str, type_str: str) -> str:
        # User Requirement: Global uniqueness across DB by NAME ONLY.
        # Ignore type_str for Identity generation.
        raw = f"{name}".strip().lower()
        return hashlib.md5(raw.encode()).hexdigest()

    def register_field(
        self, name: str, type_str: str, source_table: str, ordinal: int = 0
    ) -> Optional[str]:
        fid = self._generate_id(name, type_str)

        if fid in self.fields:
            field = self.fields[fid]
            # Existing field: Update usage and skip translation if already has EN alias
            field.update_usage(source_table)

            # Update Ordinal for this table
            if ordinal > 0:
                field.ordinals[source_table] = ordinal

            # If manually translated or auto-translated before, don't re-run expensive API
            # Especially strict if manually translated
            if field.is_manually_translated or field.aliases.get("en"):
                return None

            return None

        # Create new
        self.fields[fid] = FieldDefinition(
            id=fid,
            name=name,
            type=str(type_str),
            origin=source_table,
            usage=[source_table],
            aliases={"default": name},
            mappings={
                "pyarrow": TypeMapper.map_to_pyarrow(str(type_str)),
                "clickhouse": TypeMapper.map_to_clickhouse(str(type_str)),
            },
        )

        field = self.fields[fid]

        if ordinal > 0:
            field.ordinals[source_table] = ordinal

        # Initial English alias: Sanitized
        humanized = Sanitizer.sanitize(name)
        field.aliases["en"] = humanized

        # Attempt Auto-Translation if available
        if GoogleTranslator:
            try:
                # GoogleTranslator supports auto-detection
                translator = GoogleTranslator(source="auto", target="en")
                translated = translator.translate(humanized)

                if translated and translated != humanized:
                    field.aliases["en"] = translated
                    field.is_auto_translated = True
                    return f"Translated '{humanized}' -> '{translated}'"
                else:
                    return f"Skipped translation for '{humanized}'"

            except Exception as e:
                return f"Translation failed for '{humanized}': {e}"
        else:
            return "GoogleTranslator not available"

    def generate_target_column(self, alias: str) -> str:
        """
        Generates snake_case target column from an alias.
        Example: "Product Id" -> "product_id"
        """
        # Lowercase and replace spaces with underscores
        s1 = alias.lower().strip().replace(" ", "_")
        # Remove non-alphanumeric (keep underscores)
        s2 = re.sub(r"[^a-z0-9_]", "", s1)
        # Collapse multiple underscores
        return re.sub(r"_+", "_", s2)

    def translate_alias(self, field_id: str, target_lang: str) -> Optional[str]:
        """Translates the field name to target language"""
        if not GoogleTranslator:
            return None

        field = self.fields.get(field_id)
        if not field:
            return None

        # Enforce manual override exclusion
        if field.is_manually_translated:
            return None

        try:
            # Fallback to English alias or sanitized name as source
            text = field.aliases.get("en", Sanitizer.sanitize(field.name))

            # GoogleTranslator supports auto-detection
            translator = GoogleTranslator(source="auto", target=target_lang)
            translated = translator.translate(text)

            if translated:
                field.aliases[target_lang] = translated
                field.is_auto_translated = True
            return translated
        except Exception as e:
            print(f"Translation failed: {e}")
            return None

    def prune_fields(self, active_usages: List[str]):
        """
        Removes fields that are not used by any of the active tables.
        Also cleans up usage and ordinals for retained fields.
        """
        active_set = set(active_usages)
        to_remove = []

        for fid, field in self.fields.items():
            # 1. Check if field has any active usage
            field_usages = set(field.usage)
            intersect = field_usages.intersection(active_set)

            if not intersect:
                to_remove.append(fid)
            else:
                # 2. Retain field but clean up metadata
                field.usage = sorted(list(intersect))
                # Cleanup ordinals map
                field.ordinals = {
                    loc: ord for loc, ord in field.ordinals.items() if loc in active_set
                }
                # Cleanup context overrides
                field.context_overrides = {
                    loc: ovr
                    for loc, ovr in field.context_overrides.items()
                    if loc in active_set
                }

        for fid in to_remove:
            del self.fields[fid]

        return len(to_remove)

    def to_list(self) -> List[Dict]:
        return [f.model_dump() for f in self.fields.values()]

    def load_from_list(self, data: List[Dict]):
        for item in data:
            try:
                # 1. Parse item (might have legacy ID)
                field = FieldDefinition(**item)

                # 2. Calculate Canonical ID (Normalized by Name only)
                canonical_id = self._generate_id(field.name, field.type)

                if canonical_id in self.fields:
                    # 3. Merge into Existing Canonical Field
                    existing = self.fields[canonical_id]

                    # Merge Usage (Deduplicate)
                    for u in field.usage:
                        if u not in existing.usage:
                            existing.usage.append(u)

                    # Merge Mappings
                    existing.mappings.update(field.mappings)

                    # Merge Aliases (Prioritize Manual)
                    if field.is_manually_translated:
                        existing.aliases.update(field.aliases)
                        existing.is_manually_translated = True
                    else:
                        # Fill gaps only
                        for lang, alias in field.aliases.items():
                            if lang not in existing.aliases:
                                existing.aliases[lang] = alias

                    # Merge Target Column
                    if field.target_column and not existing.target_column:
                        existing.target_column = field.target_column

                    # Merge Ordinals
                    existing.ordinals.update(field.ordinals)

                else:
                    # 4. New Entry (Normalize ID)
                    field.id = canonical_id
                    self.fields[canonical_id] = field

            except Exception as e:
                print(f"Error loading field: {e}")
                continue

    def save_to_yaml(self, path: Any):
        import yaml
        from pathlib import Path

        path_obj = Path(path)
        path_obj.parent.mkdir(parents=True, exist_ok=True)

        data = self.to_list()
        with open(path_obj, "w") as f:
            yaml.dump(data, f, sort_keys=False)
