"""CDM Mapper — export GenerationResult as a Microsoft CDM folder structure."""

from __future__ import annotations

import json
import re
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import pandas as pd


def _to_pascal(name: str) -> str:
    """Convert snake_case to PascalCase."""
    return "".join(part.capitalize() for part in re.split(r"[_\s]+", name))


def _cdm_dtype(series: pd.Series) -> str:
    """Map a pandas dtype to a CDM dataType string."""
    dtype = series.dtype
    if pd.api.types.is_integer_dtype(dtype):
        return "int64"
    if pd.api.types.is_float_dtype(dtype):
        return "double"
    if pd.api.types.is_bool_dtype(dtype):
        return "boolean"
    if pd.api.types.is_datetime64_any_dtype(dtype):
        return "dateTime"
    if dtype.kind == "O":  # object (strings)
        # Try to detect date columns by name heuristic
        col_name = series.name if hasattr(series, "name") else ""
        if isinstance(col_name, str) and col_name.endswith("_date"):
            return "date"
        return "string"
    return "string"


class CdmEntityMap:
    """Optional mapping from source table names to CDM entity names.

    If a table is not in the map, it defaults to PascalCase of the table name.
    """

    def __init__(self, mapping: dict[str, str] | None = None):
        self._mapping = mapping or {}

    def entity_name(self, table_name: str) -> str:
        return self._mapping.get(table_name, _to_pascal(table_name))


class CdmMapper:
    """Export tables as a Microsoft CDM folder (model.json + data files).

    The output folder structure is compatible with Fabric CDM connectors,
    Dataverse, Power Platform, and Azure Data Lake Storage CDM folders.

    Usage::

        from sqllocks_spindle.transform import CdmMapper, CdmEntityMap

        mapper = CdmMapper()
        mapper.write_cdm_folder(
            tables=result.tables,
            output_dir="./cdm",
            domain_name="SpindleRetail",
            entity_map=CdmEntityMap({"customer": "Contact", "order": "SalesOrder"}),
        )
    """

    def write_cdm_folder(
        self,
        tables: dict[str, pd.DataFrame],
        output_dir: str | Path,
        domain_name: str = "SpindleOutput",
        entity_map: CdmEntityMap | None = None,
        fmt: str = "csv",
    ) -> list[Path]:
        """Write a CDM folder to disk.

        Args:
            tables: Dict of table_name → DataFrame.
            output_dir: Root directory for CDM folder output.
            domain_name: The CDM model name (appears in model.json).
            entity_map: Optional mapping of table names to CDM entity names.
            fmt: Data file format — "csv" (default) or "parquet".

        Returns:
            List of written file paths.
        """
        output_dir = Path(output_dir)
        output_dir.mkdir(parents=True, exist_ok=True)

        if entity_map is None:
            entity_map = CdmEntityMap()

        written: list[Path] = []
        entity_defs: list[dict[str, Any]] = []

        for table_name, df in tables.items():
            entity_name = entity_map.entity_name(table_name)
            entity_dir = output_dir / entity_name
            entity_dir.mkdir(exist_ok=True)

            # Write data file
            if fmt == "parquet":
                data_filename = f"{entity_name}.parquet"
                data_path = entity_dir / data_filename
                df.to_parquet(data_path, index=False)
                partition = self._parquet_partition(entity_name, data_filename)
            else:
                data_filename = f"{entity_name}.csv"
                data_path = entity_dir / data_filename
                df.to_csv(data_path, index=False)
                partition = self._csv_partition(entity_name, data_filename)

            written.append(data_path)

            # Build CDM entity definition
            attributes = [
                {"name": col, "dataType": _cdm_dtype(df[col])}
                for col in df.columns
            ]
            entity_defs.append({
                "name": entity_name,
                "$type": "LocalEntity",
                "description": f"Generated by Spindle — source table: {table_name}",
                "attributes": attributes,
                "partitions": [partition],
            })

        # Write model.json
        model = self._build_model_json(domain_name, entity_defs)
        model_path = output_dir / "model.json"
        model_path.write_text(json.dumps(model, indent=2), encoding="utf-8")
        written.append(model_path)

        return written

    def to_model_json(
        self,
        tables: dict[str, pd.DataFrame],
        domain_name: str = "SpindleOutput",
        entity_map: CdmEntityMap | None = None,
        fmt: str = "csv",
    ) -> dict[str, Any]:
        """Generate a model.json manifest dict without writing to disk.

        Useful for in-memory CDM metadata generation or Fabric notebook use.
        """
        if entity_map is None:
            entity_map = CdmEntityMap()

        entity_defs: list[dict[str, Any]] = []

        for table_name, df in tables.items():
            entity_name = entity_map.entity_name(table_name)
            data_filename = (
                f"{entity_name}.parquet" if fmt == "parquet" else f"{entity_name}.csv"
            )
            partition = (
                self._parquet_partition(entity_name, data_filename)
                if fmt == "parquet"
                else self._csv_partition(entity_name, data_filename)
            )
            attributes = [
                {"name": col, "dataType": _cdm_dtype(df[col])}
                for col in df.columns
            ]
            entity_defs.append({
                "name": entity_name,
                "$type": "LocalEntity",
                "description": f"Generated by Spindle — source table: {table_name}",
                "attributes": attributes,
                "partitions": [partition],
            })

        return self._build_model_json(domain_name, entity_defs)

    # ------------------------------------------------------------------
    # Private helpers
    # ------------------------------------------------------------------

    def _build_model_json(
        self,
        domain_name: str,
        entity_defs: list[dict[str, Any]],
    ) -> dict[str, Any]:
        return {
            "name": domain_name,
            "description": "Generated by Spindle by SQLLocks (sqllocks-spindle)",
            "version": "1.0",
            "culture": "en-US",
            "modifiedTime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
            "application": "Spindle by SQLLocks",
            "entities": entity_defs,
        }

    def _csv_partition(self, entity_name: str, filename: str) -> dict[str, Any]:
        return {
            "name": f"{entity_name}-0",
            "location": f"{entity_name}/{filename}",
            "fileFormatSettings": {
                "$type": "CsvFormatSettings",
                "columnHeaders": True,
                "delimiter": ",",
                "quoteStyle": "QuoteStyle.Csv",
                "encoding": "UTF-8",
            },
        }

    def _parquet_partition(self, entity_name: str, filename: str) -> dict[str, Any]:
        return {
            "name": f"{entity_name}-0",
            "location": f"{entity_name}/{filename}",
            "fileFormatSettings": {
                "$type": "ParquetFormatSettings",
            },
        }
