"""Persistent storage for reusable chain templates."""

import copy
import json
import uuid
from datetime import datetime
from pathlib import Path


class TemplateStore:
    """Persistent store for chain templates.

    Templates are saved to ~/.brynq/templates.json.
    """

    def __init__(self, db_path: Path | str | None = None) -> None:
        if db_path:
            self._path = Path(db_path)
            self._dir = self._path.parent
        else:
            self._dir = Path.home() / ".brynq"
            self._path = self._dir / "templates.json"
        self._templates: dict[str, dict] = {}
        self._load()

    def _load(self) -> None:
        """Load templates from disk, creating the file if missing."""
        if self._path.exists():
            data = json.loads(self._path.read_text(encoding="utf-8"))
            self._templates = {t["id"]: t for t in data}
        else:
            self._dir.mkdir(parents=True, exist_ok=True)
            self._persist()

    def _persist(self) -> None:
        """Write current templates to disk."""
        self._dir.mkdir(parents=True, exist_ok=True)
        self._path.write_text(
            json.dumps(list(self._templates.values()), indent=2),
            encoding="utf-8",
        )

    def save(
        self,
        name: str,
        chain_config: dict,
        description: str = "",
    ) -> str:
        """Save a new chain template.

        Args:
            name: Human-readable template name.
            chain_config: The chain configuration to store.
            description: Optional description of the template.

        Returns:
            The generated template ID (uuid4).
        """
        template_id = str(uuid.uuid4())
        now = datetime.now().isoformat()
        self._templates[template_id] = {
            "id": template_id,
            "name": name,
            "chain_config": chain_config,
            "description": description,
            "tags": [],
            "version": 1,
            "created_at": now,
            "updated_at": now,
        }
        self._persist()
        return template_id

    def load(self, template_id: str) -> dict | None:
        """Return the template dict for the given ID, or None if not found."""
        return self._templates.get(template_id)

    def list_all(self, tag: str | None = None) -> list[dict]:
        """Return all templates, optionally filtered by tag.

        Args:
            tag: If provided, only return templates whose tags list contains this value.

        Returns:
            List of template dicts.
        """
        templates = list(self._templates.values())
        if tag is not None:
            templates = [t for t in templates if tag in t.get("tags", [])]
        return templates

    def export_to_file(self, template_id: str, filepath: str) -> None:
        """Write a template to a JSON file with schema version metadata.

        Args:
            template_id: The template to export.
            filepath: Destination file path.

        Raises:
            ValueError: If the template ID does not exist.
        """
        template = self._templates.get(template_id)
        if template is None:
            raise ValueError(f"Template not found: {template_id}")
        data = {**template, "schema_version": "1.0"}
        Path(filepath).write_text(
            json.dumps(data, indent=2), encoding="utf-8"
        )

    def import_from_file(self, filepath: str) -> str:
        """Import a template from a JSON file.

        Reads the file, validates that it contains the required keys
        (name, chain_config), assigns a new uuid4 ID, saves it, and
        returns the new ID.

        Args:
            filepath: Path to a template JSON file.

        Returns:
            The generated template ID.

        Raises:
            ValueError: If the file is missing required keys.
        """
        data = json.loads(Path(filepath).read_text(encoding="utf-8"))
        for key in ("name", "chain_config"):
            if key not in data:
                raise ValueError(f"Template file missing required key: {key}")
        return self.save(
            name=data["name"],
            chain_config=data["chain_config"],
            description=data.get("description", ""),
        )

    def duplicate(self, template_id: str, new_name: str) -> str:
        """Create a deep copy of a template with a new ID and name.

        Args:
            template_id: The source template to duplicate.
            new_name: Name for the duplicated template.

        Returns:
            The new template ID.

        Raises:
            ValueError: If the source template is not found.
        """
        source = self._templates.get(template_id)
        if source is None:
            raise ValueError(f"Template not found: {template_id}")
        new_id = str(uuid.uuid4())
        now = datetime.now().isoformat()
        new_template = copy.deepcopy(source)
        new_template["id"] = new_id
        new_template["name"] = new_name
        new_template["created_at"] = now
        new_template["updated_at"] = now
        self._templates[new_id] = new_template
        self._persist()
        return new_id

    def tag(self, template_id: str, tags: list[str]) -> None:
        """Replace the tags list on a template.

        Args:
            template_id: The template to update.
            tags: New list of tags to set.

        Raises:
            ValueError: If the template ID does not exist.
        """
        template = self._templates.get(template_id)
        if template is None:
            raise ValueError(f"Template not found: {template_id}")
        template["tags"] = tags
        template["updated_at"] = datetime.now().isoformat()
        self._persist()

    def update_version(self, template_id: str, chain_config: dict) -> int:
        """Store the current chain_config as a version and update to the new one.

        Appends the old chain_config to a ``versions`` list on the template,
        replaces chain_config with the new value, increments the version
        number, and updates ``updated_at``.

        Args:
            template_id: The template to update.
            chain_config: The new chain configuration.

        Returns:
            The new version number.

        Raises:
            ValueError: If the template ID does not exist.
        """
        template = self._templates.get(template_id)
        if template is None:
            raise ValueError(f"Template not found: {template_id}")
        versions = template.setdefault("versions", [])
        versions.append(copy.deepcopy(template["chain_config"]))
        template["chain_config"] = chain_config
        template["version"] = template.get("version", 1) + 1
        template["updated_at"] = datetime.now().isoformat()
        self._persist()
        return template["version"]

    def get_versions(self, template_id: str) -> list[dict]:
        """Return all versions of a template.

        Each entry is a dict with ``version``, ``chain_config``, and
        ``timestamp`` keys.  Historical versions come from the internal
        ``versions`` list (populated by :meth:`update_version`), and the
        current version is always appended last.

        Args:
            template_id: The template whose versions to retrieve.

        Returns:
            List of ``{version, chain_config, timestamp}`` dicts ordered
            from oldest to newest.

        Raises:
            ValueError: If the template ID does not exist.
        """
        template = self._templates.get(template_id)
        if template is None:
            raise ValueError(f"Template not found: {template_id}")

        result: list[dict] = []
        past_configs = template.get("versions", [])
        for idx, cfg in enumerate(past_configs, start=1):
            result.append({
                "version": idx,
                "chain_config": cfg,
                "timestamp": template["created_at"],
            })
        result.append({
            "version": template.get("version", 1),
            "chain_config": template["chain_config"],
            "timestamp": template["updated_at"],
        })
        return result

    def delete(self, template_id: str) -> bool:
        """Remove a template by ID.

        Args:
            template_id: The template to delete.

        Returns:
            True if the template was found and deleted, False otherwise.
        """
        if template_id not in self._templates:
            return False
        del self._templates[template_id]
        self._persist()
        return True
