"""Tool Manager - handles file operations, header protocol, and tool persistence."""

import hashlib
import json
import re
from datetime import datetime
from pathlib import Path
from typing import Any

from anvil.models import ToolConfig, ToolMetadata, ToolStatus

# Header template for generated tools
HEADER_TEMPLATE = '''# ANVIL-MANAGED: {managed}
# version: {version}
# hash: {hash}
# ---------------------------------------------------------
# AUTO-GENERATED BY ANVIL.
# To eject and take manual control, change 'ANVIL-MANAGED' to 'false'.
# ---------------------------------------------------------

'''

# Regex patterns for parsing headers
MANAGED_PATTERN = re.compile(r"^# ANVIL-MANAGED:\s*(true|false)", re.MULTILINE)
VERSION_PATTERN = re.compile(r"^# version:\s*(.+)", re.MULTILINE)
HASH_PATTERN = re.compile(r"^# hash:\s*(.+)", re.MULTILINE)


def compute_intent_hash(intent: str) -> str:
    """Compute a short hash of the intent string."""
    return hashlib.sha256(intent.encode()).hexdigest()[:8]


class HeaderInfo:
    """Parsed header information from a tool file."""

    def __init__(
        self,
        is_managed: bool,
        version: str | None = None,
        hash: str | None = None,
    ):
        self.is_managed = is_managed
        self.version = version
        self.hash = hash


class ToolManager:
    """Manages tool files, registry, and the header protocol."""

    def __init__(self, tools_dir: str | Path = "./anvil_tools"):
        self.tools_dir = Path(tools_dir)
        self.registry_path = self.tools_dir / "tool_registry.json"
        self._ensure_tools_dir()

    def _ensure_tools_dir(self) -> None:
        """Create tools directory if it doesn't exist."""
        self.tools_dir.mkdir(parents=True, exist_ok=True)

        # Create __init__.py if missing
        init_file = self.tools_dir / "__init__.py"
        if not init_file.exists():
            init_file.write_text('"""Anvil-managed tools directory."""\n')

        # Create registry if missing
        if not self.registry_path.exists():
            self._save_registry({})

    def _load_registry(self) -> dict[str, Any]:
        """Load the tool registry from disk."""
        if not self.registry_path.exists():
            return {}
        return json.loads(self.registry_path.read_text())

    def _save_registry(self, registry: dict[str, Any]) -> None:
        """Save the tool registry to disk."""
        self.registry_path.write_text(json.dumps(registry, indent=2))

    def get_tool_path(self, name: str) -> Path:
        """Get the file path for a tool by name."""
        return self.tools_dir / f"{name}.py"

    def tool_exists(self, name: str) -> bool:
        """Check if a tool file exists on disk."""
        return self.get_tool_path(name).exists()

    def parse_header(self, name: str) -> HeaderInfo | None:
        """Parse the header from an existing tool file.

        Returns None if the file doesn't exist or has no valid header.
        """
        path = self.get_tool_path(name)
        if not path.exists():
            return None

        content = path.read_text()

        # Check for ANVIL-MANAGED marker
        managed_match = MANAGED_PATTERN.search(content)
        if not managed_match:
            # No header found - treat as user-owned (not managed)
            return HeaderInfo(is_managed=False)

        is_managed = managed_match.group(1).lower() == "true"

        # Extract version and hash
        version_match = VERSION_PATTERN.search(content)
        hash_match = HASH_PATTERN.search(content)

        return HeaderInfo(
            is_managed=is_managed,
            version=version_match.group(1) if version_match else None,
            hash=hash_match.group(1) if hash_match else None,
        )

    def is_managed(self, name: str) -> bool:
        """Check if a tool is managed by Anvil (safe to regenerate)."""
        header = self.parse_header(name)
        if header is None:
            return False  # File doesn't exist
        return header.is_managed

    def should_regenerate(self, name: str, intent: str) -> bool:
        """Determine if a tool should be regenerated.

        Returns True if:
        - Tool doesn't exist
        - Tool is managed AND intent hash changed
        """
        if not self.tool_exists(name):
            return True

        header = self.parse_header(name)
        if header is None or not header.is_managed:
            return False  # User-owned, don't touch

        # Check if intent changed
        current_hash = compute_intent_hash(intent)
        return header.hash != current_hash

    def write_tool(
        self,
        name: str,
        code: str,
        config: ToolConfig,
        version: str = "1.0",
    ) -> Path:
        """Write a generated tool to disk with proper header.

        Returns the path to the written file.
        """
        intent_hash = compute_intent_hash(config.intent)

        header = HEADER_TEMPLATE.format(
            managed="true",
            version=version,
            hash=intent_hash,
        )

        full_code = header + code
        path = self.get_tool_path(name)
        path.write_text(full_code)

        # Update registry
        self._update_registry(name, config, intent_hash, version)

        return path

    def _update_registry(
        self,
        name: str,
        config: ToolConfig,
        hash: str,
        version: str,
    ) -> None:
        """Update the tool registry with metadata."""
        registry = self._load_registry()
        now = datetime.now()

        if name in registry:
            # Update existing entry
            registry[name]["hash"] = hash
            registry[name]["version"] = version
            registry[name]["last_generated"] = now.isoformat()
            registry[name]["intent"] = config.intent
            registry[name]["docs_url"] = config.docs_url
        else:
            # New entry
            metadata = ToolMetadata(
                name=name,
                intent=config.intent,
                docs_url=config.docs_url,
                hash=hash,
                version=version,
                status=ToolStatus.ACTIVE,
                created_at=now,
                last_generated=now,
            )
            registry[name] = metadata.to_dict()

        self._save_registry(registry)

    def get_metadata(self, name: str) -> ToolMetadata | None:
        """Get metadata for a tool from the registry."""
        registry = self._load_registry()
        if name not in registry:
            return None
        return ToolMetadata.from_dict(registry[name])

    def mark_ejected(self, name: str) -> None:
        """Mark a tool as ejected (user took control)."""
        registry = self._load_registry()
        if name in registry:
            registry[name]["status"] = ToolStatus.EJECTED.value
            self._save_registry(registry)

    def read_tool_code(self, name: str) -> str | None:
        """Read the code from a tool file (excluding header)."""
        path = self.get_tool_path(name)
        if not path.exists():
            return None

        content = path.read_text()

        # Find end of header (after the second dashed line)
        # Header has two dashed lines, we want after the second one
        dash_line = "# ---------------------------------------------------------"
        first_dash = content.find(dash_line)
        if first_dash != -1:
            second_dash = content.find(dash_line, first_dash + 1)
            if second_dash != -1:
                # Find the end of that line
                newline_after = content.find("\n", second_dash)
                if newline_after != -1:
                    # Skip the blank line after header
                    return content[newline_after + 1:].lstrip("\n")

        return content
