"""
vr validate command - Validate a voice agent project structure and configuration.
"""

import ast
from pathlib import Path
from typing import List, Optional, Tuple

import typer
import yaml

from vr_cli.utils.utils import console, print_success, print_error, print_info
from vr_cli.utils.helm import (
    is_helm_available,
    require_helm,
    get_available_environments,
    render_templates,
)

ALLOWED_DEPLOYMENT_SPEC_KEYS = {
    "env",           # Environment variables
    "stt",           # Speech-to-text config (model, endpointing, language, vad, eot, etc.)
    "recording",     # Recording settings (enabled, location, redaction)
    "errorFallback", # Error fallback settings (type, value, timeout, threshold)
    "startupAudio",  # Startup audio settings (url, transcription, interruptible)
    "webhook",       # Webhook settings (url, secret)
    "region",        # Execution region
    "tracing",       # Tracing enabled
}


def templates_contain_helm_syntax(templates_dir: Path) -> bool:
    """Check if any templates contain Helm/Go template syntax."""
    if not templates_dir.exists():
        return False

    for yaml_file in list(templates_dir.glob("**/*.yaml")) + list(templates_dir.glob("**/*.yml")):
        with open(yaml_file, "r") as f:
            content = f.read()
            if "{{" in content and "}}" in content:
                return True
    return False


def strip_helm_syntax(content: str) -> str:
    """Replace Helm template expressions with placeholder values for YAML parsing."""
    import re
    # Process line by line to handle values containing Helm syntax
    lines = content.split('\n')
    result = []
    for line in lines:
        if '{{' in line and '}}' in line:
            # Find the key: value pattern and replace the entire value
            match = re.match(r'^(\s*[\w-]+:\s*)(.*)$', line)
            if match:
                key_part = match.group(1)
                value_part = match.group(2)
                if '{{' in value_part:
                    # Replace entire value with placeholder
                    line = key_part + 'HELM_PLACEHOLDER'
        result.append(line)
    return '\n'.join(result)


class ValidationResult:
    """Container for validation results."""

    def __init__(self):
        self.passed: List[str] = []
        self.failed: List[Tuple[str, str]] = []  # (check_name, error_message)

    def add_pass(self, check: str):
        self.passed.append(check)

    def add_fail(self, check: str, error: str):
        self.failed.append((check, error))

    @property
    def is_valid(self) -> bool:
        return len(self.failed) == 0


def validate_handler(project_dir: Path, result: ValidationResult):
    """Validate handler.py exists and contains async def handler."""
    handler_path = project_dir / "handler.py"

    # Check file exists
    if not handler_path.exists():
        result.add_fail("handler.py exists", "handler.py not found at project root")
        return

    result.add_pass("handler.py exists")

    # Parse as AST
    try:
        with open(handler_path, "r") as f:
            source = f.read()
        tree = ast.parse(source, filename=str(handler_path))
    except SyntaxError as e:
        result.add_fail(
            "handler.py syntax",
            f"Syntax error at line {e.lineno}: {e.msg}"
        )
        return

    # Find async def handler
    handler_found = False
    is_async = False

    for node in ast.walk(tree):
        if isinstance(node, ast.AsyncFunctionDef) and node.name == "handler":
            handler_found = True
            is_async = True
            break
        elif isinstance(node, ast.FunctionDef) and node.name == "handler":
            handler_found = True
            is_async = False
            break

    if not handler_found:
        result.add_fail(
            "handler.py contains handler function",
            "handler.py must contain 'async def handler(event, context)'"
        )
    elif not is_async:
        result.add_fail(
            "handler function is async",
            "handler function must be async (use 'async def handler')"
        )
    else:
        result.add_pass("handler.py contains async def handler")


def validate_voicerun_dir(project_dir: Path, result: ValidationResult) -> bool:
    """Validate .voicerun directory exists."""
    voicerun_dir = project_dir / ".voicerun"

    if not voicerun_dir.exists():
        result.add_fail(".voicerun/ exists", ".voicerun/ directory not found")
        return False

    result.add_pass(".voicerun/ exists")
    return True


def validate_yaml_file(file_path: Path, result: ValidationResult, check_prefix: str, strip_helm: bool = False) -> dict | None:
    """Parse and validate a YAML file, returning data or None on error."""
    try:
        with open(file_path, "r") as f:
            content = f.read()

        if strip_helm:
            content = strip_helm_syntax(content)

        data = yaml.safe_load(content)
        result.add_pass(f"{check_prefix} valid YAML")
        return data
    except yaml.YAMLError as e:
        result.add_fail(f"{check_prefix} valid YAML", f"YAML parse error: {e}")
        return None


def validate_agent_yaml(project_dir: Path, result: ValidationResult) -> dict | None:
    """Validate .voicerun/agent.yaml."""
    agent_path = project_dir / ".voicerun" / "agent.yaml"

    if not agent_path.exists():
        result.add_fail("agent.yaml exists", "agent.yaml not found in .voicerun/")
        return None

    result.add_pass("agent.yaml exists")

    data = validate_yaml_file(agent_path, result, "agent.yaml")
    if data is None:
        return None

    # Validate required fields
    if not data.get("name"):
        result.add_fail("agent.yaml name", "Missing required 'name' field")
        return None
    result.add_pass("agent.yaml has name")

    return data


def validate_templates(
    project_dir: Path,
    result: ValidationResult,
    environment: Optional[str] = None,
):
    """Validate templates directory and render if environment specified."""
    templates_dir = project_dir / ".voicerun" / "templates"

    if not templates_dir.exists():
        result.add_pass("templates/ (no templates defined)")
        return

    yaml_files = list(templates_dir.glob("**/*.yaml")) + list(templates_dir.glob("**/*.yml"))

    if not yaml_files:
        result.add_pass("templates/ (no templates defined)")
        return

    # Check if templates contain Helm syntax
    has_helm_syntax = templates_contain_helm_syntax(templates_dir)

    # If environment specified, use Helm templating
    if environment:
        if not require_helm():
            raise typer.Exit(1)

        rendered_docs = render_templates(project_dir, environment=environment)
        result.add_pass(f"templates rendered for '{environment}'")

        for data in rendered_docs:
            kind = data.get("kind")
            if not kind:
                result.add_fail("rendered template has kind", "Missing 'kind' field in rendered output")
                continue

            if kind != "Deployment":
                result.add_fail(f"rendered template valid kind", f"Unknown kind: {kind}. Only 'Deployment' is supported.")
                continue

            name = data.get("metadata", {}).get("name", "unknown")

            # Validate Deployment spec only has allowed keys
            spec = data.get("spec", {})
            invalid_keys = set(spec.keys()) - ALLOWED_DEPLOYMENT_SPEC_KEYS
            if invalid_keys:
                result.add_fail(
                    f"{kind}/{name} spec valid",
                    "Invalid spec fields: "
                    f"{', '.join(sorted(invalid_keys))}. "
                    f"Allowed fields: {', '.join(sorted(ALLOWED_DEPLOYMENT_SPEC_KEYS))}."
                )
                continue

            result.add_pass(f"rendered {kind}/{name}")

    elif has_helm_syntax:
        # Templates contain Helm syntax but no environment specified
        # Strip Helm syntax and validate YAML structure
        for yaml_file in sorted(yaml_files):
            rel_path = yaml_file.relative_to(templates_dir)
            check_prefix = f"templates/{rel_path}"

            data = validate_yaml_file(yaml_file, result, check_prefix, strip_helm=True)
            if data is None:
                continue

            # Check kind field exists (should not be templated)
            kind = data.get("kind")
            if not kind:
                result.add_fail(f"{check_prefix} has kind", "Missing 'kind' field")
                continue

            if kind != "Deployment":
                result.add_fail(f"{check_prefix} valid kind", f"Unknown kind: {kind}. Only 'Deployment' is supported.")
                continue

            # Validate Deployment spec only has allowed keys
            spec = data.get("spec", {})
            invalid_keys = set(spec.keys()) - ALLOWED_DEPLOYMENT_SPEC_KEYS
            if invalid_keys:
                result.add_fail(
                    f"{check_prefix} spec valid",
                    "Invalid spec fields: "
                    f"{', '.join(sorted(invalid_keys))}. "
                    f"Allowed fields: {', '.join(sorted(ALLOWED_DEPLOYMENT_SPEC_KEYS))}."
                )
                continue

            result.add_pass(f"{check_prefix} kind: {kind}")

    else:
        # No environment specified and no Helm syntax - validate raw templates
        for yaml_file in sorted(yaml_files):
            rel_path = yaml_file.relative_to(templates_dir)
            check_prefix = f"templates/{rel_path}"

            data = validate_yaml_file(yaml_file, result, check_prefix)
            if data is None:
                continue

            kind = data.get("kind")
            if not kind:
                result.add_fail(f"{check_prefix} has kind", "Missing 'kind' field")
                continue

            if kind != "Deployment":
                result.add_fail(f"{check_prefix} valid kind", f"Unknown kind: {kind}. Only 'Deployment' is supported.")
                continue

            # Validate Deployment spec only has allowed keys
            spec = data.get("spec", {})
            invalid_keys = set(spec.keys()) - ALLOWED_DEPLOYMENT_SPEC_KEYS
            if invalid_keys:
                result.add_fail(
                    f"{check_prefix} spec valid",
                    "Invalid spec fields: "
                    f"{', '.join(sorted(invalid_keys))}. "
                    f"Allowed fields: {', '.join(sorted(ALLOWED_DEPLOYMENT_SPEC_KEYS))}."
                )
                continue

            result.add_pass(f"{check_prefix} kind: {kind}")


def run_project_validation(
    project_dir: Path,
    *,
    include_handler: bool = True,
    environment: Optional[str] = None,
) -> ValidationResult:
    """Run the shared validation flow used by multiple commands."""
    result = ValidationResult()

    if include_handler:
        validate_handler(project_dir, result)

    if validate_voicerun_dir(project_dir, result):
        validate_agent_yaml(project_dir, result)
        validate_templates(project_dir, result, environment)

    return result


def print_results(result: ValidationResult, quiet: bool):
    """Print validation results."""
    if not quiet:
        console.print()

        # Print passes
        for check in result.passed:
            console.print(f"  [green]✓[/green] {check}")

        # Print failures
        for check, error in result.failed:
            console.print(f"  [red]✗[/red] {check}")
            console.print(f"    [dim]{error}[/dim]")

        console.print()
        console.print("━" * 40)

    # Print summary
    total_passed = len(result.passed)
    total_failed = len(result.failed)

    if result.is_valid:
        print_success(f"Validation passed ({total_passed} checks)")
    else:
        if quiet:
            # In quiet mode, print errors
            for check, error in result.failed:
                console.print(f"[red]✗[/red] {check}: {error}")
            console.print()

        print_error(f"Validation failed ({total_passed} passed, {total_failed} failed)")


def validate(
    environment: Optional[str] = typer.Option(
        None,
        "--environment",
        "-e",
        help="Environment to render templates for (e.g., development, production).",
    ),
    quiet: bool = typer.Option(
        False,
        "--quiet",
        "-q",
        help="Only output errors.",
    ),
):
    """Validate the voice agent project structure and configuration files."""
    project_dir = Path.cwd()
    voicerun_dir = project_dir / ".voicerun"
    templates_dir = voicerun_dir / "templates"

    # Auto-detect Helm templates and select environment if needed
    if not environment and templates_dir.exists() and templates_contain_helm_syntax(templates_dir):
        available_envs = get_available_environments(voicerun_dir)
        if available_envs and is_helm_available():
            # Auto-select first available environment (prefer 'development')
            if "development" in available_envs:
                environment = "development"
            else:
                environment = available_envs[0]
            if not quiet:
                print_info(f"Auto-selected environment: {environment}")

    if not quiet:
        if environment:
            console.print(f"[bold]Validating project for environment: {environment}[/bold]")
        else:
            console.print("[bold]Validating project...[/bold]")

    result = run_project_validation(
        project_dir,
        include_handler=True,
        environment=environment,
    )

    # Print results
    print_results(result, quiet)

    # Exit with appropriate code
    if not result.is_valid:
        raise typer.Exit(1)
