"""Prompt data model for promptkit."""

import hashlib
import json
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Set

from jinja2 import Template, Environment, BaseLoader, UndefinedError


@dataclass
class PromptVersion:
    """A single version of a prompt."""

    version: int
    content: str
    message: str = ""
    created_at: str = ""
    content_hash: str = ""
    variables: List[str] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        if not self.created_at:
            self.created_at = datetime.now(timezone.utc).isoformat()
        if not self.content_hash:
            self.content_hash = hashlib.sha256(self.content.encode()).hexdigest()[:12]
        if not self.variables:
            self.variables = self._extract_variables()

    def _extract_variables(self) -> List[str]:
        """Extract Jinja2 variable names from content."""
        env = Environment(loader=BaseLoader())
        try:
            ast = env.parse(self.content)
            return sorted(list(ast.find_all(
                type(ast).find_all.__func__.__code__.co_varnames[0]
                if hasattr(type(ast).find_all.__func__, '__code__') else str
            )))
        except Exception:
            pass
        # Fallback: regex-based extraction
        import re
        pattern = r'\{\{\s*(\w+)\s*\}\}'
        return sorted(list(set(re.findall(pattern, self.content))))

    def render(self, variables: Optional[Dict[str, Any]] = None) -> str:
        """Render the prompt with Jinja2 variables."""
        if not variables:
            return self.content
        try:
            template = Template(self.content)
            return template.render(**variables)
        except UndefinedError as e:
            raise ValueError(f"Missing variable: {e}")

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return asdict(self)

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "PromptVersion":
        """Create from dictionary."""
        return cls(**data)

    @property
    def short_hash(self) -> str:
        """Short hash for display."""
        return self.content_hash[:8]

    def token_estimate(self) -> int:
        """Rough token estimate (1 token ≈ 4 chars for English)."""
        return len(self.content) // 4


@dataclass
class Prompt:
    """A prompt with version history."""

    name: str
    description: str = ""
    tags: Dict[str, int] = field(default_factory=dict)  # tag_name -> version_number
    versions: List[PromptVersion] = field(default_factory=list)
    created_at: str = ""
    updated_at: str = ""
    metadata: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        now = datetime.now(timezone.utc).isoformat()
        if not self.created_at:
            self.created_at = now
        if not self.updated_at:
            self.updated_at = now

    @property
    def latest(self) -> Optional[PromptVersion]:
        """Get the latest version."""
        if not self.versions:
            return None
        return self.versions[-1]

    @property
    def version_count(self) -> int:
        """Number of versions."""
        return len(self.versions)

    def get_version(self, version: Optional[int] = None, tag: Optional[str] = None) -> Optional[PromptVersion]:
        """Get a specific version by number or tag."""
        if tag:
            if tag not in self.tags:
                return None
            version = self.tags[tag]

        if version is None:
            return self.latest

        for v in self.versions:
            if v.version == version:
                return v
        return None

    def add_version(self, content: str, message: str = "", metadata: Optional[Dict[str, Any]] = None) -> PromptVersion:
        """Add a new version."""
        next_version = len(self.versions) + 1
        version = PromptVersion(
            version=next_version,
            content=content,
            message=message or f"Version {next_version}",
            metadata=metadata or {},
        )
        self.versions.append(version)
        self.updated_at = datetime.now(timezone.utc).isoformat()

        # Auto-tag latest
        self.tags["latest"] = next_version
        return version

    def add_tag(self, tag_name: str, version: Optional[int] = None) -> None:
        """Tag a specific version."""
        if version is None:
            version = self.version_count
        if version < 1 or version > self.version_count:
            raise ValueError(f"Version {version} not found. Available: 1-{self.version_count}")
        self.tags[tag_name] = version
        self.updated_at = datetime.now(timezone.utc).isoformat()

    def remove_tag(self, tag_name: str) -> bool:
        """Remove a tag."""
        if tag_name == "latest":
            raise ValueError("Cannot remove 'latest' tag")
        if tag_name in self.tags:
            del self.tags[tag_name]
            return True
        return False

    def diff(self, v1: int, v2: int) -> List[Dict[str, Any]]:
        """Compare two versions, return diff lines."""
        ver1 = self.get_version(v1)
        ver2 = self.get_version(v2)
        if not ver1 or not ver2:
            raise ValueError(f"Version not found")

        import difflib

        lines1 = ver1.content.splitlines(keepends=True)
        lines2 = ver2.content.splitlines(keepends=True)

        diff_result = []
        for line in difflib.unified_diff(
            lines1, lines2,
            fromfile=f"v{v1} ({ver1.short_hash})",
            tofile=f"v{v2} ({ver2.short_hash})",
            lineterm="",
        ):
            if line.startswith("+++") or line.startswith("---"):
                diff_result.append({"type": "header", "content": line})
            elif line.startswith("@@"):
                diff_result.append({"type": "context", "content": line})
            elif line.startswith("+"):
                diff_result.append({"type": "add", "content": line})
            elif line.startswith("-"):
                diff_result.append({"type": "remove", "content": line})
            else:
                diff_result.append({"type": "unchanged", "content": line})

        return diff_result

    def to_dict(self) -> Dict[str, Any]:
        """Serialize to dict."""
        return {
            "name": self.name,
            "description": self.description,
            "tags": self.tags,
            "versions": [v.to_dict() for v in self.versions],
            "created_at": self.created_at,
            "updated_at": self.updated_at,
            "metadata": self.metadata,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Prompt":
        """Deserialize from dict."""
        versions = [PromptVersion.from_dict(v) for v in data.get("versions", [])]
        return cls(
            name=data["name"],
            description=data.get("description", ""),
            tags=data.get("tags", {}),
            versions=versions,
            created_at=data.get("created_at", ""),
            updated_at=data.get("updated_at", ""),
            metadata=data.get("metadata", {}),
        )
