"""Template and session data models.

Templates are reusable analysis recipes stored in a global library.
Sessions bind templates to specific test data and track user overrides.
"""

from __future__ import annotations

import copy
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any

import yaml


@dataclass
class PlotDefinition:
    """A plot definition within a template."""

    title: str = ""
    channel_patterns: list[str] = field(default_factory=list)
    transforms: list[dict[str, Any]] = field(default_factory=list)
    corridors: list[dict[str, Any]] = field(default_factory=list)
    x_cursors: tuple[float, float] | None = None
    x_range: tuple[float, float] | None = None
    y_label: str = ""


@dataclass
class TemplateSpec:
    """A template specification — reusable analysis recipe.

    Lives in the global library (~/.impakt/templates/) as YAML.
    """

    name: str
    version: int = 1
    description: str = ""
    plots: list[PlotDefinition] = field(default_factory=list)
    default_cfc: int | None = None
    criteria: list[str] = field(default_factory=list)
    protocol: str = ""
    protocol_version: str = ""
    report_format: str = "pdf"
    report_template: str = ""
    corridors: dict[str, dict[str, Any]] = field(default_factory=dict)
    metadata: dict[str, Any] = field(default_factory=dict)

    def to_yaml(self) -> str:
        """Serialize to YAML string."""
        data = {
            "name": self.name,
            "version": self.version,
            "description": self.description,
            "plots": [],
            "filters": {"default_cfc": self.default_cfc},
            "criteria": self.criteria,
            "protocol": self.protocol,
            "protocol_version": self.protocol_version,
            "report": {
                "format": self.report_format,
                "template": self.report_template,
            },
            "corridors": self.corridors,
        }

        for plot in self.plots:
            plot_data: dict[str, Any] = {"title": plot.title}
            if plot.channel_patterns:
                plot_data["channels"] = []
                for pattern in plot.channel_patterns:
                    ch_entry: dict[str, Any] = {"pattern": pattern}
                    plot_data["channels"].append(ch_entry)
                if plot.transforms:
                    plot_data["channels"][0]["transform"] = plot.transforms
            if plot.corridors:
                plot_data["corridors"] = plot.corridors
            if plot.x_cursors:
                plot_data["x_cursors"] = list(plot.x_cursors)
            data["plots"].append(plot_data)

        return yaml.dump(data, default_flow_style=False, sort_keys=False)

    @classmethod
    def from_yaml(cls, text: str) -> TemplateSpec:
        """Deserialize from YAML string."""
        data = yaml.safe_load(text)
        if not isinstance(data, dict):
            raise ValueError("Invalid template YAML")

        plots = []
        for plot_data in data.get("plots", []):
            channel_patterns = []
            transforms = []
            corridors = []

            for ch in plot_data.get("channels", []):
                if isinstance(ch, str):
                    channel_patterns.append(ch)
                elif isinstance(ch, dict):
                    channel_patterns.append(ch.get("pattern", ""))
                    transforms.extend(ch.get("transform", []))

            x_cursors = None
            raw_cursors = plot_data.get("x_cursors")
            if raw_cursors and len(raw_cursors) == 2:
                x_cursors = (float(raw_cursors[0]), float(raw_cursors[1]))

            plots.append(
                PlotDefinition(
                    title=plot_data.get("title", ""),
                    channel_patterns=channel_patterns,
                    transforms=transforms,
                    corridors=plot_data.get("corridors", []),
                    x_cursors=x_cursors,
                    y_label=plot_data.get("y_label", ""),
                )
            )

        filters = data.get("filters", {})
        report = data.get("report", {})

        return cls(
            name=data.get("name", ""),
            version=data.get("version", 1),
            description=data.get("description", ""),
            plots=plots,
            default_cfc=filters.get("default_cfc"),
            criteria=data.get("criteria", []),
            protocol=data.get("protocol", ""),
            protocol_version=data.get("protocol_version", ""),
            report_format=report.get("format", "pdf"),
            report_template=report.get("template", ""),
            corridors=data.get("corridors", {}),
        )

    def save(self, path: Path) -> None:
        """Save template to a YAML file."""
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(self.to_yaml(), encoding="utf-8")

    @classmethod
    def load(cls, path: Path) -> TemplateSpec:
        """Load template from a YAML file."""
        text = path.read_text(encoding="utf-8")
        return cls.from_yaml(text)


@dataclass
class SessionState:
    """Per-test session state.

    Stores the template reference, user overrides, and cached results.
    Lives in the test data directory at ``.impakt/session.yaml``.
    """

    template_name: str = ""
    template_version: int = 1
    test_path: str = ""
    overrides: dict[str, Any] = field(default_factory=dict)
    cached_results: dict[str, Any] = field(default_factory=dict)
    last_modified: str = ""

    def to_yaml(self) -> str:
        """Serialize to YAML."""
        data = {
            "template": self.template_name,
            "template_version": self.template_version,
            "test_path": self.test_path,
            "overrides": self.overrides,
            "cached_results": self.cached_results,
            "last_modified": self.last_modified or datetime.now().isoformat(),
        }
        return yaml.dump(data, default_flow_style=False, sort_keys=False)

    @classmethod
    def from_yaml(cls, text: str) -> SessionState:
        """Deserialize from YAML."""
        data = yaml.safe_load(text)
        if not isinstance(data, dict):
            return cls()
        return cls(
            template_name=data.get("template", ""),
            template_version=data.get("template_version", 1),
            test_path=data.get("test_path", ""),
            overrides=data.get("overrides", {}),
            cached_results=data.get("cached_results", {}),
            last_modified=data.get("last_modified", ""),
        )

    def save(self, path: Path) -> None:
        """Save session state."""
        self.last_modified = datetime.now().isoformat()
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(self.to_yaml(), encoding="utf-8")

    @classmethod
    def load(cls, path: Path) -> SessionState:
        """Load session state."""
        if not path.exists():
            return cls()
        text = path.read_text(encoding="utf-8")
        return cls.from_yaml(text)

    def set_override(self, key: str, value: Any) -> None:
        """Set a session override."""
        self.overrides[key] = value

    def get_override(self, key: str, default: Any = None) -> Any:
        """Get a session override."""
        return self.overrides.get(key, default)
