"""Validate subcommand for npkt CLI."""

import json
import sys
from pathlib import Path
from typing import Any

import click

from ..parsers.scp_parser import SCPParseError, load_policy
from .common import resolve_policy_files


@click.command()
@click.argument("policy_path", type=click.Path(exists=True))
@click.option(
    "--format",
    "-f",
    "output_format",
    type=click.Choice(["text", "json"]),
    default="text",
    help="Output format (default: text)",
)
def validate(
    policy_path: str,
    output_format: str,
) -> None:
    """Quick syntax validation of SCP files.

    Validates that SCP files are valid JSON and have the correct structure.
    Use 'npkt lint' for comprehensive policy validation.

    \b
    Examples:
      npkt validate policy.json
      npkt validate ./policies/
      npkt validate policy.json --format json
    """
    files = resolve_policy_files(policy_path)

    results = []
    all_valid = True

    for file_path in sorted(files):
        result = _validate_file(file_path)
        results.append(result)
        if not result["valid"]:
            all_valid = False

    if output_format == "json":
        _output_json(results, all_valid)
    else:
        _output_text(results, all_valid)

    sys.exit(0 if all_valid else 1)


def _validate_file(file_path: Path) -> dict[str, Any]:
    """Validate a single SCP file."""
    errors: list[str] = []
    policy_name: str | None = None
    statement_count = 0

    # Check JSON syntax
    try:
        with open(file_path, encoding="utf-8") as f:
            data = json.load(f)
    except json.JSONDecodeError as e:
        errors.append(f"Invalid JSON: {e.msg} at line {e.lineno}")
        return _build_result(file_path, errors, policy_name, statement_count)
    except OSError as e:
        errors.append(f"Cannot read file: {e}")
        return _build_result(file_path, errors, policy_name, statement_count)

    # Check basic structure
    if not isinstance(data, dict):
        errors.append("Policy must be a JSON object")
        return _build_result(file_path, errors, policy_name, statement_count)

    # Check for Version
    if "Version" not in data:
        errors.append("Missing 'Version' field")
    elif data["Version"] != "2012-10-17":
        errors.append(f"Invalid Version: {data['Version']} (expected '2012-10-17')")

    # Check for Statement
    if "Statement" not in data:
        errors.append("Missing 'Statement' field")
        return _build_result(file_path, errors, policy_name, statement_count)

    statements = data["Statement"]
    if not isinstance(statements, list):
        errors.append("'Statement' must be an array")
        return _build_result(file_path, errors, policy_name, statement_count)

    if len(statements) == 0:
        errors.append("'Statement' array is empty")
        return _build_result(file_path, errors, policy_name, statement_count)

    # Validate each statement
    for i, stmt in enumerate(statements):
        stmt_errors = _validate_statement(stmt, i)
        errors.extend(stmt_errors)

    statement_count = len(statements)

    # Try to load as SCPPolicy for additional validation
    try:
        policy = load_policy(file_path)
        policy_name = policy.name
    except (OSError, ValueError, json.JSONDecodeError, SCPParseError) as e:
        errors.append(f"Policy parsing error: {e}")

    return _build_result(file_path, errors, policy_name, statement_count)


def _build_result(
    file_path: Path,
    errors: list[str],
    policy_name: str | None,
    statement_count: int,
) -> dict[str, Any]:
    """Build a validation result dict."""
    return {
        "file": str(file_path),
        "valid": len(errors) == 0,
        "errors": errors,
        "policy_name": policy_name,
        "statement_count": statement_count,
    }


def _validate_statement(stmt: dict, index: int) -> list[str]:
    """Validate a single statement."""
    errors = []
    prefix = f"Statement[{index}]"

    if not isinstance(stmt, dict):
        return [f"{prefix}: Must be an object"]

    # Check Effect
    if "Effect" not in stmt:
        errors.append(f"{prefix}: Missing 'Effect' field")
    elif stmt["Effect"] not in ("Allow", "Deny"):
        errors.append(f"{prefix}: Invalid Effect '{stmt['Effect']}' (must be 'Allow' or 'Deny')")

    # Check for Action or NotAction
    has_action = "Action" in stmt
    has_not_action = "NotAction" in stmt

    if not has_action and not has_not_action:
        errors.append(f"{prefix}: Missing 'Action' or 'NotAction' field")
    elif has_action and has_not_action:
        errors.append(f"{prefix}: Cannot have both 'Action' and 'NotAction'")

    # Validate Action/NotAction format
    for field in ("Action", "NotAction"):
        if field in stmt:
            action_val = stmt[field]
            if isinstance(action_val, str):
                if not _is_valid_action_format(action_val):
                    errors.append(f"{prefix}: Invalid {field} format '{action_val}'")
            elif isinstance(action_val, list):
                for action in action_val:
                    if not isinstance(action, str):
                        errors.append(f"{prefix}: {field} array must contain strings")
                    elif not _is_valid_action_format(action):
                        errors.append(f"{prefix}: Invalid {field} format '{action}'")
            else:
                errors.append(f"{prefix}: {field} must be a string or array")

    # Check for Resource or NotResource
    has_resource = "Resource" in stmt
    has_not_resource = "NotResource" in stmt

    if not has_resource and not has_not_resource:
        errors.append(f"{prefix}: Missing 'Resource' or 'NotResource' field")
    elif has_resource and has_not_resource:
        errors.append(f"{prefix}: Cannot have both 'Resource' and 'NotResource'")

    return errors


def _is_valid_action_format(action: str) -> bool:
    """Check if action string has valid format."""
    if action == "*":
        return True

    # Should be service:action or service:* format
    if ":" not in action:
        return False

    parts = action.split(":", 1)
    if len(parts) != 2:
        return False

    service, action_name = parts
    if not service or not action_name:
        return False

    return True


def _output_json(results: list, all_valid: bool) -> None:
    """Output results in JSON format."""
    output = {
        "valid": all_valid,
        "files": results,
    }
    click.echo(json.dumps(output, indent=2))


def _output_text(results: list, all_valid: bool) -> None:
    """Output results in human-readable text format."""
    for result in results:
        file_path = result["file"]

        if result["valid"]:
            name = result["policy_name"] or "unnamed"
            stmts = result["statement_count"]
            click.echo(
                click.style("+ ", fg="green")
                + f"{file_path} - Valid ({name}, {stmts} statement(s))"
            )
        else:
            click.echo(click.style("X ", fg="red") + f"{file_path} - Invalid")
            for error in result["errors"]:
                click.echo(click.style(f"    {error}", fg="red"))

    click.echo()
    if all_valid:
        click.echo(click.style(f"All {len(results)} file(s) valid", fg="green"))
    else:
        invalid_count = sum(1 for r in results if not r["valid"])
        click.echo(click.style(f"{invalid_count} of {len(results)} file(s) invalid", fg="red"))
