"""BLCE Model Alteration Engine — tracked, reversible model changes.

Provides an append-only alteration log with ``undo_last()`` and
``apply_all()`` to produce an ``AlteredModel`` snapshot.
"""
from __future__ import annotations

import copy
import logging
from typing import Any, Dict, List, Optional

from .contracts import Alteration, AlteredModel

logger = logging.getLogger(__name__)


class ModelAlterationEngine:
    """Engine for tracked, reversible model changes.

    Each method appends an ``Alteration`` to the log. ``undo_last()`` pops
    the most recent alteration. ``apply_all()`` applies the full log to a
    table list and returns an ``AlteredModel``.
    """

    def __init__(self):
        self._alterations: List[Alteration] = []

    # -- alteration methods ------------------------------------------------

    def alter_table_name(
        self,
        old: str,
        new: str,
        rationale: str = "",
    ) -> Alteration:
        """Rename a table."""
        alt = Alteration(
            action="alter_table_name",
            target=old,
            old_value=old,
            new_value=new,
            rationale=rationale,
        )
        self._alterations.append(alt)
        return alt

    def alter_column_business_name(
        self,
        table: str,
        column: str,
        new_name: str,
        rationale: str = "",
    ) -> Alteration:
        """Rename a column's business-friendly name."""
        alt = Alteration(
            action="alter_column",
            target=f"{table}.{column}",
            old_value=column,
            new_value=new_name,
            rationale=rationale,
        )
        self._alterations.append(alt)
        return alt

    def alter_classification(
        self,
        table: str,
        column: str,
        new_class: str,
        rationale: str = "",
    ) -> Alteration:
        """Change a column's classification."""
        alt = Alteration(
            action="alter_classification",
            target=f"{table}.{column}",
            old_value="",
            new_value=new_class,
            rationale=rationale,
        )
        self._alterations.append(alt)
        return alt

    def add_relationship(
        self,
        src_table: str,
        src_col: str,
        tgt_table: str,
        tgt_col: str,
        rationale: str = "",
    ) -> Alteration:
        """Add a foreign-key relationship."""
        alt = Alteration(
            action="add_relationship",
            target=f"{src_table}.{src_col}",
            old_value="",
            new_value=f"{tgt_table}.{tgt_col}",
            rationale=rationale,
        )
        self._alterations.append(alt)
        return alt

    def add_business_measure(
        self,
        table: str,
        measure_name: str,
        expression: str,
        rationale: str = "",
    ) -> Alteration:
        """Add a derived business measure."""
        alt = Alteration(
            action="add_business_measure",
            target=table,
            old_value="",
            new_value=f"{measure_name}={expression}",
            rationale=rationale,
        )
        self._alterations.append(alt)
        return alt

    # -- undo --------------------------------------------------------------

    def undo_last(self) -> Optional[Alteration]:
        """Pop and return the most recent alteration, or ``None``."""
        if not self._alterations:
            return None
        return self._alterations.pop()

    # -- apply -------------------------------------------------------------

    def apply_all(
        self,
        tables: List[Dict[str, Any]],
        source_system: str = "",
    ) -> AlteredModel:
        """Apply all recorded alterations to a copy of *tables*.

        Returns an ``AlteredModel`` snapshot.
        """
        working = copy.deepcopy(tables)

        for alt in self._alterations:
            if alt.action == "alter_table_name":
                for t in working:
                    if t.get("table_name", t.get("name", "")) == alt.old_value:
                        if "table_name" in t:
                            t["table_name"] = alt.new_value
                        if "name" in t:
                            t["name"] = alt.new_value

            elif alt.action == "alter_column":
                parts = alt.target.split(".", 1)
                if len(parts) == 2:
                    tbl, col = parts
                    for t in working:
                        if t.get("table_name", t.get("name", "")) == tbl:
                            for c in t.get("columns", []):
                                if isinstance(c, dict) and c.get("name") == col:
                                    c["business_name"] = alt.new_value

            elif alt.action == "alter_classification":
                parts = alt.target.split(".", 1)
                if len(parts) == 2:
                    tbl, col = parts
                    for t in working:
                        if t.get("table_name", t.get("name", "")) == tbl:
                            for c in t.get("columns", []):
                                if isinstance(c, dict) and c.get("name") == col:
                                    c["classification"] = alt.new_value

            elif alt.action == "add_relationship":
                parts = alt.target.split(".", 1)
                if len(parts) == 2:
                    tbl, col = parts
                    for t in working:
                        if t.get("table_name", t.get("name", "")) == tbl:
                            rels = t.setdefault("relationships", [])
                            rels.append({
                                "source_column": col,
                                "target": alt.new_value,
                            })

            elif alt.action == "add_business_measure":
                for t in working:
                    if t.get("table_name", t.get("name", "")) == alt.target:
                        measures = t.setdefault("business_measures", [])
                        measures.append(alt.new_value)

        return AlteredModel(
            source_system=source_system,
            tables=working,
            alterations=list(self._alterations),
        )

    # -- queries -----------------------------------------------------------

    def get_alterations(self) -> List[Alteration]:
        """Return a copy of the alteration log."""
        return list(self._alterations)

    def get_alteration_summary(self) -> Dict[str, Any]:
        """Return counts by action type."""
        counts: Dict[str, int] = {}
        for alt in self._alterations:
            counts[alt.action] = counts.get(alt.action, 0) + 1
        return {
            "total": len(self._alterations),
            "by_action": counts,
        }
