"""
Template API Routes
Manage reusable DSL templates
"""

from typing import Dict, Any, List, Optional
from fastapi import APIRouter, HTTPException, status, Query
from pydantic import BaseModel

from opentester.core.storage import get_template_storage, get_project_storage
from opentester.models.template import (
    TemplateCreate,
    TemplateUpdate,
    TemplateResponse,
    DSLTemplate,
    TemplateVariable,
    TemplateMetadata,
)

router = APIRouter()
template_storage = get_template_storage()


class CreateTemplateRequest(BaseModel):
    """Request to create a template."""
    name: str
    description: str = ""
    target_type: str = "cli"
    dsl_template: str
    variables: List[TemplateVariable] = []
    metadata: Optional[TemplateMetadata] = None


class UpdateTemplateRequest(BaseModel):
    """Request to update a template."""
    name: Optional[str] = None
    description: Optional[str] = None
    dsl_template: Optional[str] = None
    variables: Optional[List[TemplateVariable]] = None
    metadata: Optional[TemplateMetadata] = None


class InstantiateTemplateRequest(BaseModel):
    """Request to instantiate a template."""
    project_id: str
    case_name: str
    description: Optional[str] = None
    variables: Dict[str, Any] = {}


class CreateFromCaseRequest(BaseModel):
    """Request to create template from existing case."""
    template_name: str
    description: str = ""
    category: str = "other"
    variable_mappings: Dict[str, str] = {}  # original_value -> var_name


@router.get("", response_model=List[Dict])
async def list_templates(
    category: Optional[str] = Query(None, description="Filter by category"),
    target_type: Optional[str] = Query(None, description="Filter by target type"),
    tag: Optional[str] = Query(None, description="Filter by tag"),
):
    """List all templates with optional filtering."""
    return template_storage.list_templates(
        category=category,
        target_type=target_type,
        tag=tag,
    )


@router.post("", response_model=Dict, status_code=status.HTTP_201_CREATED)
async def create_template(data: CreateTemplateRequest):
    """Create a new template."""
    template_data = TemplateCreate(
        name=data.name,
        description=data.description,
        target_type=data.target_type,
        dsl_template=data.dsl_template,
        variables=data.variables,
        metadata=data.metadata,
    )
    template = template_storage.create_template(template_data)
    return {
        "id": template.id,
        "name": template.name,
        "description": template.description,
        "target_type": template.target_type,
        "variables": [v.model_dump() for v in template.variables],
        "metadata": template.metadata.model_dump() if template.metadata else {},
        "created_at": template.created_at,
        "updated_at": template.updated_at,
    }


@router.get("/{template_id}", response_model=Dict)
async def get_template(template_id: str):
    """Get template details."""
    template = template_storage.get_template(template_id)
    if not template:
        raise HTTPException(status_code=404, detail="Template not found")

    return {
        "id": template.id,
        "name": template.name,
        "description": template.description,
        "target_type": template.target_type,
        "dsl_template": template.dsl_template,
        "variables": [v.model_dump() for v in template.variables],
        "metadata": template.metadata.model_dump() if template.metadata else {},
        "created_at": template.created_at,
        "updated_at": template.updated_at,
        "usage_count": template.usage_count,
    }


@router.put("/{template_id}", response_model=Dict)
async def update_template(template_id: str, data: UpdateTemplateRequest):
    """Update an existing template."""
    template = template_storage.get_template(template_id)
    if not template:
        raise HTTPException(status_code=404, detail="Template not found")

    update_data = TemplateUpdate(
        name=data.name,
        description=data.description,
        dsl_template=data.dsl_template,
        variables=data.variables,
        metadata=data.metadata,
    )

    updated = template_storage.update_template(template_id, update_data)
    if not updated:
        raise HTTPException(status_code=500, detail="Failed to update template")

    return {
        "id": updated.id,
        "name": updated.name,
        "description": updated.description,
        "target_type": updated.target_type,
        "variables": [v.model_dump() for v in updated.variables],
        "metadata": updated.metadata.model_dump() if updated.metadata else {},
        "updated_at": updated.updated_at,
    }


@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_template(template_id: str):
    """Delete a template."""
    template = template_storage.get_template(template_id)
    if not template:
        raise HTTPException(status_code=404, detail="Template not found")

    success = template_storage.delete_template(template_id)
    if not success:
        raise HTTPException(status_code=500, detail="Failed to delete template")


@router.post("/{template_id}/instantiate", response_model=Dict)
async def instantiate_template(template_id: str, data: InstantiateTemplateRequest):
    """Instantiate a template into a test case."""
    from datetime import datetime
    import uuid
    from opentester.models.dsl import DSLScript
    from pydantic import ValidationError
    import yaml

    template = template_storage.get_template(template_id)
    if not template:
        raise HTTPException(status_code=404, detail="Template not found")

    project_storage = get_project_storage()
    project = project_storage.get_project(data.project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")

    try:
        # Instantiate template
        dsl_yaml = template_storage.instantiate_template(template_id, data.variables)
        if not dsl_yaml:
            raise HTTPException(status_code=500, detail="Failed to instantiate template")

        # Validate DSL
        try:
            dsl_data = yaml.safe_load(dsl_yaml)
            script = DSLScript.model_validate(dsl_data)
        except yaml.YAMLError as e:
            raise HTTPException(status_code=400, detail=f"DSL YAML error: {str(e)}")
        except ValidationError as e:
            raise HTTPException(status_code=400, detail=f"DSL validation error: {str(e)}")

        # Save as test case
        case_id = str(uuid.uuid4())
        now = datetime.now().isoformat()

        if not project.cases:
            project.cases = {}

        steps = [step_data.model_dump() for step_data in script.steps]

        project.cases[case_id] = {
            "id": case_id,
            "name": data.case_name,
            "description": data.description or f"Created from template '{template.name}'",
            "dsl_content": dsl_yaml,
            "steps": steps,
            "execution_mode": "headless",
            "created_at": now,
            "updated_at": now,
            "template_id": template_id,
        }
        project_storage.save_project(project)

        return {
            "case_id": case_id,
            "name": data.case_name,
            "project_id": data.project_id,
            "template_id": template_id,
            "template_name": template.name,
            "step_count": len(steps),
            "dsl_content": dsl_yaml,
        }

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))


@router.post("/from-case/{project_id}/{case_id}", response_model=Dict)
async def create_template_from_case(
    project_id: str,
    case_id: str,
    data: CreateFromCaseRequest,
):
    """Create a template from an existing test case."""
    import re

    project_storage = get_project_storage()
    project = project_storage.get_project(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")

    case = project.cases.get(case_id) if project.cases else None
    if not case:
        raise HTTPException(status_code=404, detail="Case not found")

    dsl_content = case.get("dsl_content", "")
    if not dsl_content:
        raise HTTPException(status_code=400, detail="Case has no DSL content")

    # Replace specified values with variables
    variables = []
    if data.variable_mappings:
        for original, var_name in data.variable_mappings.items():
            if original in dsl_content:
                dsl_content = dsl_content.replace(original, f"${{{var_name}}}")
                variables.append(TemplateVariable(
                    name=var_name,
                    description=f"Replaces '{original[:30]}...'" if len(original) > 30 else f"Replaces '{original}'",
                    required=True,
                ))

    # Extract any remaining ${var} patterns
    var_pattern = r'\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}'
    existing_vars = set(v.name for v in variables)
    for var_name in re.findall(var_pattern, dsl_content):
        if var_name not in existing_vars:
            variables.append(TemplateVariable(
                name=var_name,
                description=f"Variable {var_name}",
                required=True,
            ))

    metadata = TemplateMetadata(
        category=data.category,
        tags=["from-case", project.name],
    )

    template_data = TemplateCreate(
        name=data.template_name,
        description=data.description or f"Created from case '{case.get('name')}'",
        target_type=project.target.type if project.target else "cli",
        dsl_template=dsl_content,
        variables=variables,
        metadata=metadata,
    )

    template = template_storage.create_template(template_data)

    return {
        "id": template.id,
        "name": template.name,
        "description": template.description,
        "target_type": template.target_type,
        "variable_count": len(template.variables),
        "source_case": case.get("name"),
        "source_project": project.name,
    }


@router.get("/categories/list", response_model=List[str])
async def list_template_categories():
    """List all template categories."""
    return ["cli", "api", "tui", "gui", "auth", "database", "workflow", "other"]
