#!/usr/bin/env python3
"""
Schema validation CLI for pre-commit hooks and CI/CD.

Usage:
    # Validate all schemas (syntax and structure)
    python -m lightwave.schema.cli

    # Validate specific files (for pre-commit)
    python -m lightwave.schema.cli definitions/layouts.yaml definitions/pages.yaml

    # Type-check: Cross-reference validation across all files
    python -m lightwave.schema.cli --type-check

    # Full validation (syntax + type-check)
    python -m lightwave.schema.cli --type-check --strict

    # Verbose output
    python -m lightwave.schema.cli --verbose

    # Strict mode (treat warnings as errors)
    python -m lightwave.schema.cli --strict

    # As pre-commit hook
    pre-commit run schema-lint

Exit codes:
    0 - All schemas valid
    1 - Validation errors found
    2 - Invalid arguments or file not found
"""

from __future__ import annotations

import argparse
import sys
from pathlib import Path
from typing import NamedTuple

import yaml

# Schema definitions directory (from centralized resolver)
from lightwave.schema.core.paths import DEFINITIONS_DIR

SCHEMA_DIR = DEFINITIONS_DIR

# Known schema files and their expected structure
SCHEMA_FILES = {
    "layouts.yaml": {
        "required_keys": ["_meta", "layouts"],
        "db_backed": True,
    },
    "blueprints.yaml": {
        "required_keys": ["_meta", "blueprints"],
        "db_backed": True,
    },
    "pages.yaml": {
        "required_keys": ["_meta", "defaults", "tenants"],
        "db_backed": True,
    },
    "routes.yaml": {
        "required_keys": ["_meta", "routes"],
        "db_backed": False,
    },
    "navigation.yaml": {
        "required_keys": ["_meta", "sites"],
        "db_backed": True,
    },
    "features.yaml": {
        "required_keys": ["_meta", "feature_definitions", "tenants"],
        "db_backed": True,
    },
    "user_flows.yaml": {
        "required_keys": ["_meta", "user_flows"],
        "db_backed": False,
    },
    "rbac.yaml": {
        "required_keys": ["user_roles"],
        "db_backed": False,
    },
    "domains.yaml": {
        "required_keys": ["environments", "tenants"],
        "db_backed": False,
    },
    "email.yaml": {
        "required_keys": ["_meta", "webhook_events", "outbound_addresses", "routing_rules"],
        "db_backed": False,
    },
    "security.yaml": {
        "required_keys": ["_meta", "jwt", "sessions", "rate_limits", "api_keys"],
        "db_backed": False,
    },
    "assets.yaml": {
        "required_keys": ["_meta", "cdn", "image_optimization", "cache_control", "upload"],
        "db_backed": False,
    },
    "websockets.yaml": {
        "required_keys": ["_meta", "connection", "channels", "message_types", "error_codes"],
        "db_backed": False,
    },
}


class ValidationResult(NamedTuple):
    """Result of schema validation."""

    file_path: Path
    errors: list[str]
    warnings: list[str]

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

    @property
    def has_warnings(self) -> bool:
        return len(self.warnings) > 0


def validate_yaml_syntax(file_path: Path) -> tuple[dict | None, str | None]:
    """
    Check if a file contains valid YAML syntax.

    Returns:
        Tuple of (parsed_data, error_message)
    """
    try:
        with open(file_path) as f:
            data = yaml.safe_load(f)
        return data, None
    except yaml.YAMLError as e:
        return None, f"YAML syntax error: {e}"
    except FileNotFoundError:
        return None, f"File not found: {file_path}"
    except PermissionError:
        return None, f"Permission denied: {file_path}"


def validate_meta_section(data: dict, file_name: str, is_db_backed: bool) -> list[str]:
    """Validate the _meta section of a schema file."""
    errors = []

    meta = data.get("_meta")
    if meta is None:
        # Some schemas (rbac.yaml, domains.yaml) don't have _meta
        if file_name in ("rbac.yaml", "domains.yaml"):
            return []
        errors.append("Missing required '_meta' section")
        return errors

    # Check required _meta fields
    if "version" not in meta:
        errors.append("Missing '_meta.version'")

    if "model" not in meta:
        errors.append("Missing '_meta.model' (use null for non-DB-backed schemas)")

    # key_field only required for DB-backed schemas
    model = meta.get("model")
    if model is not None and "key_field" not in meta:
        errors.append("Missing '_meta.key_field' (required when model is specified)")

    return errors


def validate_layouts_schema(data: dict) -> list[str]:
    """Validate layouts.yaml specific structure."""
    errors = []

    layouts = data.get("layouts", {})
    expected_layouts = {"web", "auth", "app", "app_admin", "error"}
    found_layouts = set(layouts.keys())

    missing = expected_layouts - found_layouts
    extra = found_layouts - expected_layouts

    if missing:
        errors.append(f"Missing required layouts: {sorted(missing)}")
    if extra:
        errors.append(f"Unexpected layouts (only 5 allowed): {sorted(extra)}")

    for name, layout in layouts.items():
        if "template_path" not in layout:
            errors.append(f"Layout '{name}' missing 'template_path'")

    return errors


def validate_blueprints_schema(data: dict) -> list[str]:
    """Validate blueprints.yaml specific structure."""
    errors = []

    blueprints = data.get("blueprints", {})
    for name, blueprint in blueprints.items():
        if "schema" not in blueprint:
            errors.append(f"Blueprint '{name}' missing JSONSchema 'schema' definition")
        if "allowed_layouts" not in blueprint:
            errors.append(f"Blueprint '{name}' missing 'allowed_layouts'")

    return errors


def validate_pages_schema(data: dict) -> list[str]:
    """Validate pages.yaml specific structure."""
    errors = []

    tenants = data.get("tenants", {})
    for tenant_name, tenant_data in tenants.items():
        # Support two formats:
        # 1. Single site: { site: "domain.com", pages: [...] }
        # 2. Multi-site: { sites: { "domain1.com": { pages: [...] }, ... } }

        if "sites" in tenant_data:
            # Multi-site format
            for site_domain, site_data in tenant_data.get("sites", {}).items():
                pages = site_data.get("pages", [])
                for i, page in enumerate(pages):
                    if "path" not in page:
                        errors.append(f"Tenant '{tenant_name}' site '{site_domain}' page {i + 1} missing 'path'")
                    if "title" not in page:
                        errors.append(f"Tenant '{tenant_name}' site '{site_domain}' page {i + 1} missing 'title'")
        elif "site" in tenant_data:
            # Single site format
            pages = tenant_data.get("pages", [])
            for i, page in enumerate(pages):
                if "path" not in page:
                    errors.append(f"Tenant '{tenant_name}' page {i + 1} missing 'path'")
                if "title" not in page:
                    errors.append(f"Tenant '{tenant_name}' page {i + 1} missing 'title'")
        else:
            errors.append(f"Tenant '{tenant_name}' missing 'site' or 'sites' configuration")

    return errors


def validate_user_flows_schema(data: dict) -> list[str]:
    """Validate user_flows.yaml specific structure."""
    errors = []

    flows = data.get("user_flows", {})
    for flow_id, flow in flows.items():
        if "name" not in flow:
            errors.append(f"Flow '{flow_id}' missing 'name'")
        if "steps" not in flow:
            errors.append(f"Flow '{flow_id}' missing 'steps'")
            continue

        steps = flow.get("steps", [])
        for i, step in enumerate(steps):
            step_num = step.get("step", i + 1)
            if "endpoint" not in step and "url" not in step:
                errors.append(f"Flow '{flow_id}' step {step_num} missing 'endpoint' or 'url'")
            if "method" not in step:
                errors.append(f"Flow '{flow_id}' step {step_num} missing 'method'")

    return errors


def validate_features_schema(data: dict) -> list[str]:
    """Validate features.yaml specific structure."""
    errors = []

    definitions = data.get("feature_definitions", {})
    tenants = data.get("tenants", {})

    # Check that all tenant features are defined
    for tenant_name, tenant_data in tenants.items():
        features = tenant_data.get("features", {})
        for feature_name in features:
            if feature_name not in definitions:
                errors.append(f"Tenant '{tenant_name}' uses undefined feature '{feature_name}'")

    return errors


def validate_navigation_schema(data: dict) -> list[str]:
    """Validate navigation.yaml specific structure."""
    errors = []

    sites = data.get("sites", {})
    for site_name, site_data in sites.items():
        for location, items in site_data.items():
            if not isinstance(items, list):
                continue
            for i, item in enumerate(items):
                if "label" not in item and "title" not in item:
                    errors.append(f"Site '{site_name}' {location} item {i + 1} missing 'label' or 'title'")

    return errors


def validate_schema_file(file_path: Path, verbose: bool = False) -> ValidationResult:
    """
    Validate a single schema file.

    Args:
        file_path: Path to the YAML file
        verbose: Whether to include extra validation details

    Returns:
        ValidationResult with errors and warnings
    """
    errors: list[str] = []
    warnings: list[str] = []

    file_name = file_path.name

    # Check if this is a known schema file
    schema_config = SCHEMA_FILES.get(file_name)
    if schema_config is None:
        # Unknown schema file - just validate YAML syntax
        data, error = validate_yaml_syntax(file_path)
        if error:
            errors.append(error)
        return ValidationResult(file_path, errors, warnings)

    # Parse YAML
    data, error = validate_yaml_syntax(file_path)
    if error:
        errors.append(error)
        return ValidationResult(file_path, errors, warnings)

    if data is None:
        errors.append("File is empty or contains only null")
        return ValidationResult(file_path, errors, warnings)

    # Check required top-level keys
    required_keys = schema_config.get("required_keys", [])
    for key in required_keys:
        if key not in data:
            errors.append(f"Missing required key '{key}'")

    # Validate _meta section
    is_db_backed = schema_config.get("db_backed", True)
    meta_errors = validate_meta_section(data, file_name, is_db_backed)
    errors.extend(meta_errors)

    # Schema-specific validation
    if file_name == "layouts.yaml":
        errors.extend(validate_layouts_schema(data))
    elif file_name == "blueprints.yaml":
        errors.extend(validate_blueprints_schema(data))
    elif file_name == "pages.yaml":
        errors.extend(validate_pages_schema(data))
    elif file_name == "user_flows.yaml":
        errors.extend(validate_user_flows_schema(data))
    elif file_name == "features.yaml":
        errors.extend(validate_features_schema(data))
    elif file_name == "navigation.yaml":
        errors.extend(validate_navigation_schema(data))

    return ValidationResult(file_path, errors, warnings)


def find_schema_files(
    paths: list[str] | None = None,
    schema_dir: Path | None = None,
) -> list[Path]:
    """
    Find schema files to validate.

    Args:
        paths: Optional list of specific file paths. If None, validates all in schema_dir.
        schema_dir: Directory to search for schema files.

    Returns:
        List of Path objects to validate
    """
    base_dir = schema_dir or SCHEMA_DIR

    if paths:
        # Validate specific files
        result = []
        for p in paths:
            path = Path(p)
            if path.exists():
                result.append(path)
            elif (base_dir / path.name).exists():
                result.append(base_dir / path.name)
            else:
                # Could be relative to cwd
                cwd_path = Path.cwd() / p
                if cwd_path.exists():
                    result.append(cwd_path)
        return result
    else:
        # Validate all known schema files
        result = []
        for filename in SCHEMA_FILES:
            file_path = base_dir / filename
            if file_path.exists():
                result.append(file_path)
        return result


def main(args: list[str] | None = None) -> int:
    """
    Main entry point for the CLI.

    Args:
        args: Command line arguments (defaults to sys.argv[1:])

    Returns:
        Exit code (0 for success, 1 for errors, 2 for invalid args)
    """
    parser = argparse.ArgumentParser(
        description="Validate LightWave YAML schema files",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )
    parser.add_argument(
        "files",
        nargs="*",
        help="Specific schema files to validate (default: all)",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help="Show detailed validation output",
    )
    parser.add_argument(
        "-s",
        "--strict",
        action="store_true",
        help="Treat warnings as errors",
    )
    parser.add_argument(
        "-q",
        "--quiet",
        action="store_true",
        help="Only show errors, no success messages",
    )
    parser.add_argument(
        "--schema-dir",
        type=Path,
        help=f"Custom schema directory (default: {SCHEMA_DIR})",
    )
    parser.add_argument(
        "-t",
        "--type-check",
        action="store_true",
        help="Run cross-reference type checking across all schema files",
    )

    parsed = parser.parse_args(args)

    # Override schema dir if specified
    schema_dir = parsed.schema_dir if parsed.schema_dir else SCHEMA_DIR

    # Find files to validate
    files = find_schema_files(parsed.files if parsed.files else None, schema_dir=schema_dir)

    if not files:
        if not parsed.quiet:
            print("No schema files found to validate", file=sys.stderr)
        return 2

    # Validate each file
    results: list[ValidationResult] = []
    for file_path in files:
        result = validate_schema_file(file_path, verbose=parsed.verbose)
        results.append(result)

    # Print results
    total_errors = 0
    total_warnings = 0

    for result in results:
        has_issues = result.errors or (parsed.strict and result.warnings)
        total_errors += len(result.errors)
        total_warnings += len(result.warnings)

        if has_issues:
            print(f"❌ {result.file_path.name}:")
            for error in result.errors:
                print(f"   ERROR: {error}")
            for warning in result.warnings:
                prefix = "ERROR" if parsed.strict else "WARN"
                print(f"   {prefix}: {warning}")
        elif not parsed.quiet:
            print(f"✅ {result.file_path.name}")

    # Summary
    if not parsed.quiet:
        print()
        if total_errors == 0 and (not parsed.strict or total_warnings == 0):
            print(f"All {len(results)} schema files valid!")
        else:
            issues = total_errors + (total_warnings if parsed.strict else 0)
            print(f"Found {issues} issue(s) in {len(results)} file(s)")

    # Exit code for syntax validation
    exit_code = 0
    if total_errors > 0:
        exit_code = 1
    if parsed.strict and total_warnings > 0:
        exit_code = 1

    # Run type-check if requested
    if parsed.type_check:
        type_check_exit = run_type_check(schema_dir, parsed.verbose, parsed.strict, parsed.quiet)
        if type_check_exit > exit_code:
            exit_code = type_check_exit

    return exit_code


def run_type_check(schema_dir: Path, verbose: bool, strict: bool, quiet: bool) -> int:
    """
    Run cross-reference type checking across all schema files.

    Returns:
        Exit code (0 for success, 1 for errors)
    """
    try:
        from .cross_validator import CrossReferenceValidator
    except ImportError:
        print("❌ Type checking requires pydantic. Install with: pip install pydantic", file=sys.stderr)
        return 2

    if not quiet:
        print("\n" + "=" * 60)
        print("CROSS-REFERENCE TYPE CHECK")
        print("=" * 60)

    validator = CrossReferenceValidator(schema_dir=schema_dir)
    report = validator.validate_all()

    # Print issues
    if report.issues:
        print(f"\n❌ Found {len(report.issues)} type error(s):\n")
        for issue in report.issues:
            print(f"  {issue.file}:{issue.location}")
            print(f"    {issue.issue_type}: {issue.message}")
            print(f"    Value: {issue.value}")
            if issue.suggestion:
                print(f"    Suggestion: {issue.suggestion}")
            print()

    # Print warnings
    if report.warnings:
        prefix = "❌" if strict else "⚠️"
        label = "error" if strict else "warning"
        print(f"\n{prefix} Found {len(report.warnings)} {label}(s):\n")
        for warning in report.warnings:
            print(f"  {warning.file}:{warning.location}")
            print(f"    {warning.issue_type}: {warning.message}")
            print(f"    Value: {warning.value}")
            if warning.suggestion and verbose:
                print(f"    Suggestion: {warning.suggestion}")
            print()

    # Print stats
    if verbose and not quiet:
        print("\nValidation Stats:")
        for key, value in report.stats.items():
            print(f"  {key}: {value}")

    # Summary
    if not quiet:
        print()
        if report.is_valid and (not strict or not report.has_warnings):
            print("✅ All cross-references valid!")
        else:
            issue_count = len(report.issues) + (len(report.warnings) if strict else 0)
            print(f"Found {issue_count} cross-reference issue(s)")

    # Exit code
    if report.issues:
        return 1
    if strict and report.warnings:
        return 1
    return 0


if __name__ == "__main__":
    sys.exit(main())
