"""
Phase 53: Reusable Task Templates for Common Operations.

Define parameterized task templates that can be instantiated with variable
substitution. Templates track usage counts for popularity ranking and can
be cloned.

Storage:
  state/broker/task_templates/
    {name}.json              — individual template definitions
    instantiation_log.jsonl  — log of every template instantiation
"""

import json
import logging
import string
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

from brynq.server import BROKER_DIR, _write_json_sync, _read_jsonl_sync, _append_jsonl_sync

logger = logging.getLogger(__name__)

# ── Paths ──────────────────────────────────────────────────────────────────

TEMPLATES_DIR = BROKER_DIR / "task_templates"
INSTANTIATION_LOG = TEMPLATES_DIR / "instantiation_log.jsonl"

TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)

VALID_PRIORITIES = {"critical", "high", "normal", "low"}


# ── Internal helpers ──────────────────────────────────────────────────────

def _template_file(name: str) -> Path:
    return TEMPLATES_DIR / f"{name}.json"


def _read_template(name: str) -> Optional[dict]:
    path = _template_file(name)
    if not path.exists():
        return None
    try:
        return json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def _save_template(name: str, data: dict) -> None:
    _write_json_sync(_template_file(name), data)


def _substitute(pattern: str, variables: Dict[str, str]) -> str:
    """Replace $variable references in a pattern string."""
    result = pattern
    for key, val in variables.items():
        result = result.replace(f"${key}", str(val))
    return result


# ── Public API ────────────────────────────────────────────────────────────

def create_template(
    name: str,
    title_pattern: str,
    description_pattern: str,
    required_skills: List[str],
    default_priority: str = "normal",
    estimated_minutes: int = 30,
    category: Optional[str] = None,
) -> dict:
    """Define a new task template.

    Args:
        name: Unique template name.
        title_pattern: Title with $variable placeholders.
        description_pattern: Description with $variable placeholders.
        required_skills: Skills needed to work on tasks from this template.
        default_priority: Default priority for instantiated tasks.
        estimated_minutes: Estimated time to complete.
        category: Optional category for grouping templates.

    Returns:
        The created template record.
    """
    if default_priority not in VALID_PRIORITIES:
        raise ValueError(f"Invalid priority '{default_priority}'. Must be one of {VALID_PRIORITIES}")

    if _read_template(name) is not None:
        raise ValueError(f"Template '{name}' already exists")

    now = datetime.now(timezone.utc).isoformat()
    template = {
        "name": name,
        "title_pattern": title_pattern,
        "description_pattern": description_pattern,
        "required_skills": required_skills,
        "default_priority": default_priority,
        "estimated_minutes": estimated_minutes,
        "category": category,
        "created_at": now,
        "updated_at": now,
        "use_count": 0,
    }

    _save_template(name, template)
    logger.info("Created template '%s'", name)
    return template


def instantiate(
    template_name: str,
    variables: Optional[Dict[str, str]] = None,
) -> dict:
    """Create a task from a template, substituting $variables.

    Args:
        template_name: Name of the template to instantiate.
        variables: Dict mapping variable names to values.

    Returns:
        A task dict ready for submission.
    """
    template = _read_template(template_name)
    if template is None:
        raise ValueError(f"Template '{template_name}' not found")

    vars_ = variables or {}
    title = _substitute(template["title_pattern"], vars_)
    description = _substitute(template["description_pattern"], vars_)

    task_id = uuid.uuid4().hex[:12]
    now = datetime.now(timezone.utc).isoformat()

    task = {
        "task_id": task_id,
        "title": title,
        "description": description,
        "required_skills": template["required_skills"],
        "priority": template["default_priority"],
        "estimated_minutes": template["estimated_minutes"],
        "template_name": template_name,
        "variables": vars_,
        "created_at": now,
    }

    # Update use count
    template["use_count"] = template.get("use_count", 0) + 1
    template["updated_at"] = now
    _save_template(template_name, template)

    # Log instantiation
    _append_jsonl_sync(INSTANTIATION_LOG, {
        "template_name": template_name,
        "task_id": task_id,
        "variables": vars_,
        "timestamp": now,
    })

    logger.info("Instantiated template '%s' as task %s", template_name, task_id)
    return task


def list_templates(category: Optional[str] = None) -> List[dict]:
    """List all templates, optionally filtered by category."""
    templates = []
    for path in TEMPLATES_DIR.glob("*.json"):
        if path.name == "instantiation_log.jsonl":
            continue
        try:
            data = json.loads(path.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            continue
        if category is not None and data.get("category") != category:
            continue
        templates.append(data)

    templates.sort(key=lambda t: t.get("name", ""))
    return templates


def get_template(name: str) -> Optional[dict]:
    """Get a single template by name."""
    return _read_template(name)


def delete_template(name: str) -> bool:
    """Delete a template. Returns True if it existed."""
    path = _template_file(name)
    if not path.exists():
        return False
    path.unlink()
    logger.info("Deleted template '%s'", name)
    return True


def get_popular_templates(limit: int = 10) -> List[dict]:
    """Return the most-instantiated templates."""
    templates = list_templates()
    templates.sort(key=lambda t: t.get("use_count", 0), reverse=True)
    return templates[:limit]


def clone_template(source_name: str, new_name: str) -> dict:
    """Copy a template under a new name.

    Args:
        source_name: Name of template to copy.
        new_name: Name for the new template.

    Returns:
        The cloned template record.
    """
    source = _read_template(source_name)
    if source is None:
        raise ValueError(f"Template '{source_name}' not found")

    if _read_template(new_name) is not None:
        raise ValueError(f"Template '{new_name}' already exists")

    now = datetime.now(timezone.utc).isoformat()
    clone = dict(source)
    clone["name"] = new_name
    clone["created_at"] = now
    clone["updated_at"] = now
    clone["use_count"] = 0

    _save_template(new_name, clone)
    logger.info("Cloned template '%s' -> '%s'", source_name, new_name)
    return clone
