"""
Data models for Eddytor REST API requests and responses.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any


# =============================================================================
# Enums
# =============================================================================


class SchemeType(str, Enum):
    """Storage scheme types."""

    S3 = "S3"
    ABFSS = "Abfss"
    GS = "Gs"


class AIProvider(str, Enum):
    """AI provider types."""

    CLAUDE = "Claude"
    OPENAI = "OpenAI"
    GEMINI = "Gemini"
    MISTRAL = "Mistral"


class AIAction(str, Enum):
    """AI analysis actions."""

    SUMMARY = "Summary"
    DETECT_ANOMALIES = "DetectAnomalies"
    FIND_DUPLICATES = "FindDuplicates"
    FIX_NULLS = "FixNulls"
    EXPLAIN_ROWS = "ExplainRows"


class DomainType(str, Enum):
    """Column domain types."""

    FIXED = "Fixed"
    HIERARCHICAL_INLINE = "HierarchicalInline"
    HIERARCHICAL_DERIVED = "HierarchicalDerived"
    HIERARCHICAL_TABLE = "HierarchicalTable"


# =============================================================================
# Table Models
# =============================================================================


@dataclass
class ColumnInfo:
    """Information about a table column."""

    name: str
    data_type: str
    nullable: bool = True
    metadata: dict[str, Any] | None = None


@dataclass
class TableMetadata:
    """Metadata for a Delta table."""

    catalog: str
    schema: str
    name: str
    columns: list[ColumnInfo]
    partition_columns: list[str] = field(default_factory=list)
    row_count: int | None = None
    size_bytes: int | None = None
    created_at: str | None = None
    updated_at: str | None = None

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> TableMetadata:
        columns = [
            ColumnInfo(
                name=c.get("name", ""),
                data_type=c.get("data_type", c.get("dataType", "")),
                nullable=c.get("nullable", True),
                metadata=c.get("metadata"),
            )
            for c in data.get("columns", [])
        ]
        return cls(
            catalog=data.get("catalog", ""),
            schema=data.get("schema", ""),
            name=data.get("name", ""),
            columns=columns,
            partition_columns=data.get("partition_columns", data.get("partitionColumns", [])),
            row_count=data.get("row_count", data.get("rowCount")),
            size_bytes=data.get("size_bytes", data.get("sizeBytes")),
            created_at=data.get("created_at", data.get("createdAt")),
            updated_at=data.get("updated_at", data.get("updatedAt")),
        )


@dataclass
class TableHistoryEntry:
    """A single entry in table history."""

    version: int
    timestamp: int
    operation: str
    user_name: str | None = None
    user_id: str | None = None
    comment: str | None = None
    operation_parameters: dict[str, Any] | None = None
    operation_metrics: dict[str, Any] | None = None

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> TableHistoryEntry:
        info = data.get("info") or {}
        comment = info.get("comment")
        if comment == "null":
            comment = None
        return cls(
            version=data.get("version", 0),
            timestamp=data.get("timestamp", 0),
            operation=data.get("operation", ""),
            user_name=data.get("userName"),
            user_id=data.get("userId"),
            comment=comment,
            operation_parameters=data.get("operationParameters"),
            operation_metrics=info.get("operationMetrics"),
        )


@dataclass
class TableHistory:
    """History of table operations."""

    entries: list[TableHistoryEntry]

    @classmethod
    def from_dict(cls, data: dict[str, Any] | list) -> TableHistory:
        if isinstance(data, list):
            items = data
        else:
            items = data.get("entries", data.get("history", []))
        return cls(entries=[TableHistoryEntry.from_dict(e) for e in items])


@dataclass
class InferredSchema:
    """Schema inferred from a file."""

    columns: list[ColumnInfo]
    has_header: bool
    row_count: int | None = None

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> InferredSchema:
        columns = [
            ColumnInfo(
                name=c.get("name", ""),
                data_type=c.get("data_type", c.get("dataType", "")),
                nullable=c.get("nullable", True),
            )
            for c in data.get("columns", [])
        ]
        return cls(
            columns=columns,
            has_header=data.get("has_header", data.get("hasHeader", True)),
            row_count=data.get("row_count", data.get("rowCount")),
        )


@dataclass
class MagicDustResult:
    """Result from AI analysis."""

    action: str
    model: str
    content: str
    input_tokens: int | None = None
    output_tokens: int | None = None

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> MagicDustResult:
        usage = data.get("usage", {})
        return cls(
            action=data.get("action", ""),
            model=data.get("model", ""),
            content=data.get("content", ""),
            input_tokens=usage.get("input_tokens", usage.get("inputTokens")) if usage else None,
            output_tokens=usage.get("output_tokens", usage.get("outputTokens")) if usage else None,
        )


@dataclass
class CellReference:
    """A tagged reference to a specific cell value in the explain rows response."""

    id: int
    column: str
    row_index: int
    value: str
    display: str

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> CellReference:
        return cls(
            id=data.get("id", 0),
            column=data.get("column", ""),
            row_index=data.get("row_index", data.get("rowIndex", 0)),
            value=str(data.get("value", "")),
            display=data.get("display", ""),
        )


@dataclass
class ExplainRowsResult:
    """Result from the explain rows AI action."""

    explanation: str
    references: list[CellReference]
    confidence: float
    model: str
    input_tokens: int | None = None
    output_tokens: int | None = None

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> ExplainRowsResult:
        usage = data.get("usage", {})
        refs = [CellReference.from_dict(r) for r in data.get("references", [])]
        return cls(
            explanation=data.get("explanation", ""),
            references=refs,
            confidence=data.get("confidence", 0.0),
            model=data.get("model", ""),
            input_tokens=usage.get("input_tokens", usage.get("inputTokens")) if usage else None,
            output_tokens=usage.get("output_tokens", usage.get("outputTokens")) if usage else None,
        )


@dataclass
class MoveTableResult:
    """Result from moving a Delta table to a different storage config."""

    objects_moved: int
    bytes_transferred: int
    message: str

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> MoveTableResult:
        return cls(
            objects_moved=data.get("objects_moved", data.get("objectsMoved", 0)),
            bytes_transferred=data.get("bytes_transferred", data.get("bytesTransferred", 0)),
            message=data.get("message", ""),
        )


@dataclass
class MoveObjectsResult:
    """Result from moving objects between storage configurations."""

    objects_moved: int
    bytes_transferred: int
    tables_deregistered: int
    discovery_triggered: bool
    message: str

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> MoveObjectsResult:
        return cls(
            objects_moved=data.get("objects_moved", data.get("objectsMoved", 0)),
            bytes_transferred=data.get("bytes_transferred", data.get("bytesTransferred", 0)),
            tables_deregistered=data.get("tables_deregistered", data.get("tablesDeregistered", 0)),
            discovery_triggered=data.get("discovery_triggered", data.get("discoveryTriggered", False)),
            message=data.get("message", ""),
        )


# =============================================================================
# Storage Models
# =============================================================================


@dataclass
class StorageConfig:
    """A user's storage configuration."""

    id: str
    name: str
    path: str
    scheme_type: SchemeType
    created_at: str

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> StorageConfig:
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            path=data.get("path", ""),
            scheme_type=SchemeType(data.get("scheme_type", data.get("schemeType", "S3"))),
            created_at=data.get("created_at", data.get("createdAt", "")),
        )


@dataclass
class RegisteredTable:
    """A discovered table from the file explorer tree.

    Each table has a three-part **Fully Qualified Name (FQN)**::

        eddytor.cfg_<config_id_hex>.<hash>_<table_name>

    - **catalog** — always ``"eddytor"``.
    - **schema** — ``cfg_`` + storage config UUID (dashes removed).
    - **name** — hash prefix + human-readable table name.

    Use the :pyattr:`fqn` property to get the SQL-safe identifier, or pass
    ``catalog``, ``schema``, and ``name`` to any SDK method that needs them.
    """

    name: str
    path: str
    catalog: str = ""
    schema: str = ""
    table_type: str = ""
    scheme_type: str = ""
    size_bytes: int = 0
    last_modified: str = ""
    estimated_rows: int | None = None

    @property
    def fqn(self) -> str:
        """Fully-qualified SQL name: ```catalog`.`schema`.`name```."""
        return f"`{self.catalog}`.`{self.schema}`.`{self.name}`"

    def __repr__(self) -> str:
        return f"RegisteredTable({self.fqn})"

    @classmethod
    def from_discovered(cls, data: dict[str, Any]) -> RegisteredTable:
        """Parse a DiscoveredTable node from the file explorer JSON."""
        ref = data.get("tableReference") or {}
        table_type_obj = data.get("tableType", {})
        return cls(
            name=data.get("name", ""),
            path=data.get("path", ""),
            catalog=ref.get("catalog", ""),
            schema=ref.get("schema", ""),
            table_type=table_type_obj.get("type", ""),
            scheme_type=data.get("schemeType", ""),
            size_bytes=data.get("sizeBytes", 0),
            last_modified=data.get("lastModified", ""),
            estimated_rows=data.get("estimatedRows"),
        )

    @classmethod
    def collect_from_explorer(cls, data: dict[str, Any]) -> list[RegisteredTable]:
        """Recursively walk a file explorer response and collect all tables."""
        tables: list[RegisteredTable] = []
        explorer = data.get("fileExplorer", data)
        cls._walk(explorer, tables)
        return tables

    @classmethod
    def _walk(cls, node: dict[str, Any], out: list[RegisteredTable]) -> None:
        children = node.get("children")
        if children is None:
            # Leaf node — this is a DiscoveredTable
            out.append(cls.from_discovered(node))
            return
        for child in children.values():
            cls._walk(child, out)


@dataclass
class StorageObject:
    """An object in cloud storage."""

    path: str
    name: str
    size: int
    last_modified: str | None = None
    is_directory: bool = False

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> StorageObject:
        return cls(
            path=data.get("path", ""),
            name=data.get("name", ""),
            size=data.get("size", 0),
            last_modified=data.get("last_modified", data.get("lastModified")),
            is_directory=data.get("is_directory", data.get("isDirectory", False)),
        )


@dataclass
class ListObjectsResult:
    """Result from listing storage objects."""

    objects: list[StorageObject]
    directories: list[str]
    continuation_token: str | None = None

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> ListObjectsResult:
        objects = [StorageObject.from_dict(o) for o in data.get("objects", [])]
        return cls(
            objects=objects,
            directories=data.get("directories", []),
            continuation_token=data.get("continuation_token", data.get("continuationToken")),
        )


# =============================================================================
# Column Domain Models
# =============================================================================


@dataclass
class HierarchyNode:
    """A node in a hierarchical domain."""

    value: str
    children: list[str] = field(default_factory=list)


@dataclass
class ColumnDomain:
    """Column domain configuration."""

    domain_type: DomainType | None = None
    values: list[str] | None = None
    hierarchy: list[HierarchyNode] | None = None
    source_table: str | None = None
    source_column: str | None = None
    parent_column: str | None = None

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> ColumnDomain:
        domain_type = data.get("domain_type", data.get("domainType"))
        hierarchy = None
        if "hierarchy" in data and data["hierarchy"]:
            hierarchy = [
                HierarchyNode(
                    value=h.get("value", h.get("parent", "")),
                    children=h.get("children", []),
                )
                for h in data["hierarchy"]
            ]
        return cls(
            domain_type=DomainType(domain_type) if domain_type else None,
            values=data.get("values"),
            hierarchy=hierarchy,
            source_table=data.get("source_table", data.get("sourceTable")),
            source_column=data.get("source_column", data.get("sourceColumn")),
            parent_column=data.get("parent_column", data.get("parentColumn")),
        )


@dataclass
class AllowedValues:
    """Allowed values for a column."""

    values: list[str]
    parent_value: str | None = None

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> AllowedValues:
        return cls(
            values=data.get("values", []),
            parent_value=data.get("parent_value", data.get("parentValue")),
        )
