"""PyAuthz CLI entry point.

Provides commands for validating policies, simulating decisions, and
inspecting the authorization configuration.

Usage:
    pyauthz check policies/          # Validate policy files
    pyauthz simulate policies/ \\
        --subject alice --roles admin \\
        --action documents:write \\
        --resource doc-1 --resource-type documents
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from typing import NoReturn

from pyauthz.core.engine import PyAuthz
from pyauthz.core.errors import ConfigError, PolicyVersionError, SchemaError
from pyauthz.models.resource import Resource
from pyauthz.models.subject import Subject


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

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

    Returns:
        Exit code (0 = success, 1 = error).
    """
    parser = argparse.ArgumentParser(
        prog="pyauthz",
        description="PyAuthz authorization CLI — validate policies and simulate decisions.",
    )
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # --- check command ---
    check_parser = subparsers.add_parser(
        "check",
        help="Validate policy YAML files for syntax and schema errors.",
    )
    check_parser.add_argument(
        "path",
        help="Path to a YAML file or directory of YAML files.",
    )
    check_parser.add_argument(
        "--verbose",
        "-v",
        action="store_true",
        help="Show detailed information about loaded policies and roles.",
    )

    # --- simulate command ---
    sim_parser = subparsers.add_parser(
        "simulate",
        help="Simulate an authorization decision against loaded policies.",
    )
    sim_parser.add_argument(
        "path",
        help="Path to a YAML file or directory of YAML files.",
    )
    sim_parser.add_argument(
        "--subject",
        "-s",
        required=True,
        help="Subject ID.",
    )
    sim_parser.add_argument(
        "--roles",
        "-r",
        default="",
        help="Comma-separated list of roles for the subject.",
    )
    sim_parser.add_argument(
        "--action",
        "-a",
        required=True,
        help="Action to check (e.g., 'documents:read').",
    )
    sim_parser.add_argument(
        "--resource-id",
        default="",
        help="Resource ID.",
    )
    sim_parser.add_argument(
        "--resource-type",
        "-t",
        default="default",
        help="Resource type.",
    )
    sim_parser.add_argument(
        "--resource-owner",
        default="",
        help="Resource owner ID.",
    )
    sim_parser.add_argument(
        "--env",
        "-e",
        action="append",
        default=[],
        help="Environment key=value pairs (can be repeated).",
    )
    sim_parser.add_argument(
        "--json",
        action="store_true",
        dest="output_json",
        help="Output result as JSON.",
    )

    # --- roles command ---
    roles_parser = subparsers.add_parser(
        "roles",
        help="List roles loaded from policy files.",
    )
    roles_parser.add_argument(
        "path",
        help="Path to a YAML file or directory of YAML files.",
    )

    # --- policies command ---
    policies_parser = subparsers.add_parser(
        "policies",
        help="List policies loaded from policy files.",
    )
    policies_parser.add_argument(
        "path",
        help="Path to a YAML file or directory of YAML files.",
    )

    # --- lint command ---
    lint_parser = subparsers.add_parser(
        "lint",
        help="Lint policies for common mistakes and issues.",
    )
    lint_parser.add_argument(
        "path",
        help="Path to a YAML file or directory of YAML files.",
    )

    args = parser.parse_args(argv)

    if args.command is None:
        parser.print_help()
        return 1

    if args.command == "check":
        return _cmd_check(args)
    if args.command == "simulate":
        return _cmd_simulate(args)
    if args.command == "roles":
        return _cmd_roles(args)
    if args.command == "policies":
        return _cmd_policies(args)
    if args.command == "lint":
        return _cmd_lint(args)

    return 1


def _load_engine(path: str) -> PyAuthz | None:
    """Load policies from path into a PyAuthz engine.

    Returns None and prints errors on failure.
    """
    rp = PyAuthz()
    try:
        rp.load_policies(path)
    except (SchemaError, PolicyVersionError, ConfigError) as e:
        print(f"ERROR: {e}", file=sys.stderr)
        return None
    except FileNotFoundError as e:
        print(f"ERROR: {e}", file=sys.stderr)
        return None
    return rp


def _cmd_check(args: argparse.Namespace) -> int:
    """Validate policy files."""
    path = args.path
    if not Path(path).exists():
        print(f"ERROR: Path '{path}' does not exist.", file=sys.stderr)
        return 1

    rp = _load_engine(path)
    if rp is None:
        return 1

    roles = rp._storage.get_roles()
    policies = rp._storage.get_policies()

    print(f"OK: Loaded {len(roles)} role(s) and {len(policies)} policy/ies from '{path}'.")

    if args.verbose:
        if roles:
            print("\nRoles:")
            for name, role in sorted(roles.items()):
                parents = f" (parents: {', '.join(role.parents)})" if role.parents else ""
                print(f"  {name}: {', '.join(role.permissions)}{parents}")
        if policies:
            print("\nPolicies:")
            for policy in policies:
                cond_count = len(policy.conditions) if policy.conditions else 0
                cond_info = f", {cond_count} condition(s)" if cond_count else ""
                print(
                    f"  {policy.name}: {policy.effect.value} "
                    f"[{', '.join(policy.permissions or ['*'])}]{cond_info}"
                )

    return 0


def _cmd_simulate(args: argparse.Namespace) -> int:
    """Simulate an authorization decision."""
    rp = _load_engine(args.path)
    if rp is None:
        return 1

    roles = [r.strip() for r in args.roles.split(",") if r.strip()]
    subject = Subject(id=args.subject, roles=roles)
    resource = Resource(
        id=args.resource_id,
        type=args.resource_type,
        owner_id=args.resource_owner,
    )

    # Parse environment
    env: dict[str, str] = {}
    for pair in args.env:
        if "=" in pair:
            key, value = pair.split("=", 1)
            env[key] = value
        else:
            print(
                f"WARNING: Ignoring invalid env pair '{pair}' (expected key=value)",
                file=sys.stderr,
            )

    decision = rp._evaluate(subject, args.action, resource, env)

    if args.output_json:
        result = {
            "allowed": decision.allowed,
            "effect": decision.effect.value,
            "matched_policies": decision.matched_policies,
            "reasoning": decision.reasoning,
            "duration_us": decision.duration_us,
        }
        print(json.dumps(result, indent=2))
    else:
        status = "ALLOWED" if decision.allowed else "DENIED"
        print(f"{status}: {subject.id} -> {args.action} on {resource.type}/{resource.id}")
        if decision.matched_policies:
            print(f"  Matched policies: {', '.join(decision.matched_policies)}")
        if decision.reasoning:
            for reason in decision.reasoning:
                print(f"  - {reason}")

    return 0


def _cmd_roles(args: argparse.Namespace) -> int:
    """List loaded roles."""
    rp = _load_engine(args.path)
    if rp is None:
        return 1

    roles = rp._storage.get_roles()
    if not roles:
        print("No roles found.")
        return 0

    for name, role in sorted(roles.items()):
        parents = f" (inherits: {', '.join(role.parents)})" if role.parents else ""
        perms = ", ".join(role.permissions) if role.permissions else "(no permissions)"
        print(f"  {name}: {perms}{parents}")

    return 0


def _cmd_policies(args: argparse.Namespace) -> int:
    """List loaded policies."""
    rp = _load_engine(args.path)
    if rp is None:
        return 1

    policies = rp._storage.get_policies()
    if not policies:
        print("No policies found.")
        return 0

    for policy in policies:
        roles_str = ", ".join(policy.roles) if policy.roles else "*"
        perms_str = ", ".join(policy.permissions) if policy.permissions else "*"
        conds = len(policy.conditions) if policy.conditions else 0
        enabled = "" if policy.enabled else " [DISABLED]"
        cond_info = f", {conds} condition(s)" if conds else ""
        print(
            f"  {policy.name}: {policy.effect.value} roles=[{roles_str}] perms=[{perms_str}]{cond_info}{enabled}"
        )

    return 0


def _cmd_lint(args: argparse.Namespace) -> int:
    """Lint policies for common issues."""
    from pyauthz.core.lint import lint_policies

    rp = _load_engine(args.path)
    if rp is None:
        return 1

    issues = lint_policies(rp)
    if not issues:
        print("No issues found.")
        return 0

    for issue in issues:
        print(str(issue))

    error_count = sum(1 for i in issues if i.severity.value == "error")
    return 1 if error_count > 0 else 0


def cli_entry() -> NoReturn:
    """Entry point for console_scripts."""
    sys.exit(main())
