from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional, Tuple

from auditwalk.cli.handlers.preflight_handlers import preflight_export, preflight_run, preflight_show
from auditwalk.cli.handlers.scan_handlers import scan_full, scan_paranoid, scan_quick


@dataclass(frozen=True)
class ArgSpec:
    name: str
    help: str = ""
    required: bool = True
    nargs: Optional[str] = None


@dataclass(frozen=True)
class OptSpec:
    flags: Tuple[str, ...]
    help: str = ""
    takes_value: bool = True
    default: Optional[Any] = None
    choices: Optional[Tuple[str, ...]] = None


@dataclass(frozen=True)
class CommandSpec:
    noun: str
    verb: str
    summary: str
    handler: Callable[..., int]
    args: Tuple[ArgSpec, ...] = ()
    opts: Tuple[OptSpec, ...] = ()

    @property
    def key(self) -> Tuple[str, str]:
        return (self.noun, self.verb)

    @property
    def path(self) -> str:
        return f"{self.noun} {self.verb}"


@dataclass
class CommandRegistry:
    commands: Dict[Tuple[str, str], CommandSpec] = field(default_factory=dict)
    noun_order: List[str] = field(default_factory=list)

    def add(self, spec: CommandSpec) -> None:
        if spec.key in self.commands:
            raise ValueError(f"Duplicate command registered: {spec.path}")
        self.commands[spec.key] = spec
        if spec.noun not in self.noun_order:
            self.noun_order.append(spec.noun)

    def get(self, noun: str, verb: str) -> CommandSpec:
        try:
            return self.commands[(noun, verb)]
        except KeyError as exc:
            raise KeyError(f"Unknown command: {noun} {verb}") from exc

    def nouns(self) -> List[str]:
        return list(self.noun_order)

    def verbs(self, noun: str) -> List[str]:
        verbs = [v for (n, v) in self.commands.keys() if n == noun]
        priority = {"full": 0, "quick": 1, "paranoid": 2, "run": 3, "show": 4, "list": 5, "create": 6, "delete": 7, "export": 8, "render": 9}
        return sorted(verbs, key=lambda x: (priority.get(x, 50), x))

    def specs_for_noun(self, noun: str) -> List[CommandSpec]:
        return [self.commands[(noun, verb)] for verb in self.verbs(noun)]

    def all_specs(self) -> List[CommandSpec]:
        out: List[CommandSpec] = []
        for noun in self.nouns():
            out.extend(self.specs_for_noun(noun))
        return out


def _not_implemented(*_a: Any, **_kw: Any) -> int:
    print("Not implemented yet.")
    return 2


_SCAN_COMMON_OPTS = (
    OptSpec(("--roots",), help="Root directories to scan.", default=None),
    OptSpec(("--follow-symlinks",), help="Follow symlinks.", takes_value=False, default=False),
    OptSpec(("--exclude",), help="Directory names to exclude anywhere in the path.", default=None),
    OptSpec(("--report-dir",), help="Directory to write reports.", default=None),
    OptSpec(("--no-report",), help="Do not write a JSON report file.", takes_value=False, default=False),
)


def build_registry() -> CommandRegistry:
    registry = CommandRegistry()

    registry.add(CommandSpec(
        noun="scan",
        verb="full",
        summary="Run the full scan profile.",
        handler=scan_full,
        opts=_SCAN_COMMON_OPTS,
    ))
    registry.add(CommandSpec(
        noun="scan",
        verb="quick",
        summary="Run a quick scan profile.",
        handler=scan_quick,
        opts=_SCAN_COMMON_OPTS,
    ))
    registry.add(CommandSpec(
        noun="scan",
        verb="paranoid",
        summary="Run the deepest scan profile.",
        handler=scan_paranoid,
        opts=_SCAN_COMMON_OPTS,
    ))

    registry.add(CommandSpec(
        noun="preflight",
        verb="run",
        summary="Run a quick trust-state evaluation of the system.",
        handler=preflight_run,
        opts=(
            OptSpec(("--format",), help="Output format.", choices=("text", "json"), default="text"),
            OptSpec(("--out",), help="Base output directory for AuditWalk data (optional).", default=None),
        ),
    ))
    registry.add(CommandSpec(
        noun="preflight",
        verb="show",
        summary="Show the most recent preflight result.",
        handler=preflight_show,
        opts=(
            OptSpec(("--format",), help="Output format.", choices=("text", "json"), default="text"),
            OptSpec(("--out",), help="Base output directory for AuditWalk data (optional).", default=None),
        ),
    ))
    registry.add(CommandSpec(
        noun="preflight",
        verb="export",
        summary="Export the most recent preflight result as JSON.",
        handler=preflight_export,
        opts=(
            OptSpec(("--format",), help="Export format.", choices=("json",), default="json"),
            OptSpec(("--out",), help="Destination file or directory (optional).", default=None),
        ),
    ))

    registry.add(CommandSpec(noun="baseline", verb="create", summary="Create a baseline snapshot.", handler=_not_implemented))
    registry.add(CommandSpec(noun="baseline", verb="list", summary="List available baselines.", handler=_not_implemented))
    registry.add(CommandSpec(noun="baseline", verb="show", summary="Show baseline details.", handler=_not_implemented))
    registry.add(CommandSpec(noun="baseline", verb="delete", summary="Delete a baseline.", handler=_not_implemented))

    registry.add(CommandSpec(noun="compare", verb="run", summary="Compare two runs or baselines.", handler=_not_implemented))
    registry.add(CommandSpec(noun="compare", verb="last", summary="Compare latest run vs latest baseline.", handler=_not_implemented))
    registry.add(CommandSpec(noun="compare", verb="show", summary="Show compare details.", handler=_not_implemented))
    registry.add(CommandSpec(noun="compare", verb="export", summary="Export compare details.", handler=_not_implemented))

    registry.add(CommandSpec(noun="risk", verb="run", summary="Run risk scoring modules.", handler=_not_implemented))
    registry.add(CommandSpec(noun="risk", verb="show", summary="Show latest risk result.", handler=_not_implemented))
    registry.add(CommandSpec(noun="risk", verb="export", summary="Export latest risk result.", handler=_not_implemented))

    registry.add(CommandSpec(noun="report", verb="render", summary="Render human-readable report.", handler=_not_implemented))
    registry.add(CommandSpec(noun="report", verb="list", summary="List recent reports.", handler=_not_implemented))

    return registry
