#!/usr/bin/env python3
"""Main CLI entry point for MAID Runner.

Provides a unified command-line interface with subcommands:
- maid --version
- maid init ...
- maid validate ...
- maid snapshot ...
- maid snapshot-system ...
- maid test ...
- maid manifests ...
- maid schema
- maid manifest create ...
"""

import argparse
import sys
from pathlib import Path

from maid_runner import __version__
from maid_runner.cli.validate import (
    run_coherence_validation,
    _format_coherence_issues,
    format_coherence_json,
)


def setup_graph_parser(subparsers: argparse._SubParsersAction) -> None:
    """Set up the graph subparser with query, export, and analysis subcommands.

    Args:
        subparsers: The subparsers action from the main argument parser.

    Returns:
        None
    """
    # Graph subparser
    graph_parser = subparsers.add_parser(
        "graph",
        help="Knowledge graph operations",
        description="Query, export, and analyze the knowledge graph built from manifests",
    )

    # Nested subparsers for graph command
    graph_subparsers = graph_parser.add_subparsers(
        dest="subcommand", help="Graph subcommands"
    )

    # Query subcommand
    query_parser = graph_subparsers.add_parser(
        "query",
        help="Query the knowledge graph",
        description="Execute a query against the knowledge graph",
    )
    query_parser.add_argument(
        "query",
        help="The query string to execute",
    )
    query_parser.add_argument(
        "--manifest-dir",
        default="manifests",
        help="Directory containing manifests (default: manifests)",
    )

    # Export subcommand
    export_parser = graph_subparsers.add_parser(
        "export",
        help="Export the knowledge graph",
        description="Export the knowledge graph to a file in various formats",
    )
    export_parser.add_argument(
        "--format",
        choices=["json", "dot", "graphml"],
        required=True,
        help="Output format (json, dot, or graphml)",
    )
    export_parser.add_argument(
        "--output",
        required=True,
        help="Output file path",
    )
    export_parser.add_argument(
        "--manifest-dir",
        default="manifests",
        help="Directory containing manifests (default: manifests)",
    )

    # Analysis subcommand
    analysis_parser = graph_subparsers.add_parser(
        "analysis",
        help="Run graph analysis",
        description="Run analysis on the knowledge graph",
    )
    analysis_parser.add_argument(
        "--type",
        dest="analysis_type",
        choices=["find-cycles", "show-stats"],
        required=True,
        help="Type of analysis to run",
    )
    analysis_parser.add_argument(
        "--manifest-dir",
        default="manifests",
        help="Directory containing manifests (default: manifests)",
    )


def add_coherence_arguments(parser: argparse.ArgumentParser) -> None:
    """Add coherence validation arguments to an argument parser.

    Adds --coherence and --coherence-only flags to the validate subparser.
    These flags enable architectural coherence validation.

    Args:
        parser: The ArgumentParser to add arguments to.

    Returns:
        None
    """
    parser.add_argument(
        "--coherence",
        action="store_true",
        default=False,
        help="Run coherence validation in addition to standard validation",
    )
    parser.add_argument(
        "--coherence-only",
        action="store_true",
        default=False,
        help="Run only coherence validation (skip standard validation)",
    )
    parser.add_argument(
        "--format",
        choices=["text", "json"],
        default="text",
        help="Output format for coherence validation (default: text)",
    )


def handle_coherence_validation(args: argparse.Namespace) -> bool:
    """Handle coherence validation based on parsed arguments.

    Checks if coherence validation is requested and runs it if so.
    Automatically enables use_manifest_chain when coherence validation
    is requested.

    Args:
        args: Parsed arguments namespace with coherence, coherence_only,
              use_manifest_chain, manifest_path, manifest_dir, quiet,
              and format attributes.

    Returns:
        True if coherence validation passes or is not requested, False otherwise.
    """
    # Check if coherence validation is requested
    if not args.coherence and not args.coherence_only:
        return True

    # Auto-enable use_manifest_chain when coherence is requested
    args.use_manifest_chain = True

    # Determine manifest path and directory
    manifest_path = getattr(args, "manifest_path", None)
    manifest_dir = getattr(args, "manifest_dir", None)
    quiet = getattr(args, "quiet", False)
    output_format = getattr(args, "format", "text")

    # If no manifest path is provided, we cannot run coherence validation
    if not manifest_path:
        # For directory validation, skip coherence validation for now
        return True

    manifest_path_obj = Path(manifest_path)
    if not manifest_path_obj.exists():
        return True

    # Determine manifest directory
    if manifest_dir:
        manifest_dir_path = Path(manifest_dir)
    else:
        manifest_dir_path = manifest_path_obj.parent

    # Run coherence validation
    result = run_coherence_validation(manifest_path_obj, manifest_dir_path, quiet)

    # Format and display output based on format option
    if output_format == "json":
        print(format_coherence_json(result, manifest_path_obj))
    elif not quiet:
        _format_coherence_issues(result, quiet)

    return result.valid


def main():
    """Main CLI entry point with subcommands."""
    parser = argparse.ArgumentParser(
        prog="maid",
        description="MAID Runner - Manifest-driven AI Development validation tool",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    parser.add_argument(
        "--version",
        action="version",
        version=f"maid-runner {__version__}",
        help="Show version and exit",
    )

    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # Validate subcommand
    validate_parser = subparsers.add_parser(
        "validate",
        help="Validate manifest against implementation or behavioral test files",
        description="Validate manifest against implementation or behavioral test files",
    )
    validate_parser.add_argument(
        "manifest_path",
        nargs="?",
        help="Path to the manifest JSON file (mutually exclusive with --manifest-dir)",
    )
    validate_parser.add_argument(
        "--validation-mode",
        choices=["implementation", "behavioral", "schema"],
        default="implementation",
        help="Validation mode: 'implementation' (default) checks definitions, 'behavioral' checks usage, 'schema' validates manifest structure only",
    )
    validate_parser.add_argument(
        "--use-manifest-chain",
        action="store_true",
        help="Use manifest chain to merge all related manifests (enables file tracking analysis; automatically enabled for directory validation)",
    )
    validate_parser.add_argument(
        "--quiet",
        "-q",
        action="store_true",
        help="Only output errors (suppress success messages)",
    )
    validate_parser.add_argument(
        "--manifest-dir",
        help="Directory containing manifests to validate (mutually exclusive with manifest_path)",
    )
    validate_parser.add_argument(
        "--watch",
        "-w",
        action="store_true",
        help="Watch mode: automatically re-run validation when manifest changes (requires manifest_path)",
    )
    validate_parser.add_argument(
        "--watch-all",
        action="store_true",
        help="Watch all manifests: automatically re-run validation when any manifest changes",
    )
    validate_parser.add_argument(
        "--timeout",
        type=int,
        default=300,
        help="Command timeout in seconds (default: 300)",
    )
    validate_parser.add_argument(
        "--verbose",
        "-v",
        action="store_true",
        help="Show detailed command output",
    )
    validate_parser.add_argument(
        "--skip-tests",
        action="store_true",
        help="Skip running validationCommand after validation (watch modes only)",
    )
    validate_parser.add_argument(
        "--use-cache",
        action="store_true",
        default=False,
        help="Enable manifest chain caching for improved performance",
    )

    validate_parser.add_argument(
        "--json-output",
        action="store_true",
        help="Output validation results as JSON (for tool integration)",
    )

    # Add coherence validation arguments
    add_coherence_arguments(validate_parser)

    # Snapshot subcommand
    snapshot_parser = subparsers.add_parser(
        "snapshot",
        help="Generate MAID snapshot manifests from existing Python or TypeScript files",
        description="Generate MAID snapshot manifests from existing Python or TypeScript files",
    )
    snapshot_parser.add_argument(
        "file_path",
        help="Path to the Python (.py) or TypeScript (.ts, .tsx, .js, .jsx) file to snapshot",
    )
    snapshot_parser.add_argument(
        "--output-dir",
        default="manifests",
        help="Directory to write the manifest (default: manifests)",
    )
    snapshot_parser.add_argument(
        "--force",
        action="store_true",
        help="Overwrite existing manifests without prompting",
    )
    snapshot_parser.add_argument(
        "--skip-test-stub",
        action="store_true",
        help="Skip test stub generation (stubs are generated by default)",
    )

    # Snapshot-system subcommand
    snapshot_system_parser = subparsers.add_parser(
        "snapshot-system",
        help="Generate system-wide manifest snapshot from all active manifests",
        description="Generate system-wide manifest snapshot aggregating artifacts and validation commands from all active manifests",
    )
    snapshot_system_parser.add_argument(
        "--output",
        default="system.manifest.json",
        help="Output file path for the system manifest (default: system.manifest.json)",
    )
    snapshot_system_parser.add_argument(
        "--manifest-dir",
        default="manifests",
        help="Directory containing manifests (default: manifests)",
    )
    snapshot_system_parser.add_argument(
        "--quiet",
        "-q",
        action="store_true",
        help="Suppress informational output (errors still shown)",
    )

    # Test subcommand
    test_parser = subparsers.add_parser(
        "test",
        help="Run validation commands from all non-superseded manifests (uses batch mode for speed)",
        description="""Run validation commands from all non-superseded manifests.

When running multiple manifests, automatically uses batch mode to run all pytest
tests in a single invocation (10-20x faster). Falls back to sequential mode for
mixed test runners (pytest + vitest, etc.).""",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    test_parser.add_argument(
        "--manifest",
        "-m",
        help="Run validation commands for a single manifest (filename relative to manifest-dir or absolute path)",
    )
    test_parser.add_argument(
        "--manifest-dir",
        default="manifests",
        help="Directory containing manifests (default: manifests)",
    )
    test_parser.add_argument(
        "--fail-fast",
        action="store_true",
        help="Stop execution on first failure",
    )
    test_parser.add_argument(
        "--verbose",
        "-v",
        action="store_true",
        help="Show detailed command output",
    )
    test_parser.add_argument(
        "--quiet",
        "-q",
        action="store_true",
        help="Only show summary (suppress per-manifest output)",
    )
    test_parser.add_argument(
        "--timeout",
        type=int,
        default=300,
        help="Command timeout in seconds (default: 300)",
    )
    test_parser.add_argument(
        "--watch",
        "-w",
        action="store_true",
        help="Watch mode: automatically re-run validation commands when files change (requires --manifest)",
    )
    test_parser.add_argument(
        "--watch-all",
        action="store_true",
        help="Watch all manifests: automatically re-run affected validation commands when any tracked file changes",
    )

    # List-manifests subcommand
    list_manifests_parser = subparsers.add_parser(
        "manifests",
        help="List all manifests that reference a given file",
        description="List all manifests that reference a given file, categorized by how they reference it (created, edited, or read)",
    )
    list_manifests_parser.add_argument(
        "file_path", help="Path to the file to search for in manifests"
    )
    list_manifests_parser.add_argument(
        "--manifest-dir",
        default="manifests",
        help="Directory containing manifests (default: manifests)",
    )
    list_manifests_parser.add_argument(
        "--quiet",
        "-q",
        action="store_true",
        help="Show minimal output (just manifest names)",
    )
    list_manifests_parser.add_argument(
        "--json-output",
        action="store_true",
        help="Output manifest list as JSON array",
    )

    # Init subcommand
    init_parser = subparsers.add_parser(
        "init",
        help="Initialize MAID methodology in an existing repository",
        description="Initialize MAID methodology by creating directory structure and documentation",
    )
    init_parser.add_argument(
        "--target-dir",
        default=".",
        help="Target directory to initialize (default: current directory)",
    )
    init_parser.add_argument(
        "--force",
        action="store_true",
        help="Overwrite existing files without prompting",
    )
    init_parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Show which files would be created or updated without making changes",
    )
    init_parser.add_argument(
        "--claude",
        action="store_true",
        help="Set up Claude Code integration (default if no tool specified)",
    )
    init_parser.add_argument(
        "--cursor",
        action="store_true",
        help="Set up Cursor IDE rules",
    )
    init_parser.add_argument(
        "--windsurf",
        action="store_true",
        help="Set up Windsurf IDE rules",
    )
    init_parser.add_argument(
        "--generic",
        action="store_true",
        help="Create generic MAID.md documentation file",
    )
    init_parser.add_argument(
        "--all",
        action="store_true",
        help="Set up all supported dev tools",
    )

    # Howto subcommand
    howto_parser = subparsers.add_parser(
        "howto",
        help="Interactive guide to MAID methodology",
        description="Display an interactive walkthrough of the MAID methodology",
    )
    howto_parser.add_argument(
        "--section",
        help="Jump directly to a specific section (intro|principles|workflow|quickstart|patterns|commands|troubleshooting)",
    )

    # Generate-stubs subcommand
    generate_stubs_parser = subparsers.add_parser(
        "generate-stubs",
        help="Generate test stubs from existing manifest",
        description="Generate failing test stubs from an existing manifest file",
    )
    generate_stubs_parser.add_argument(
        "manifest_path", help="Path to the manifest file"
    )

    # Schema subcommand
    subparsers.add_parser(
        "schema",
        help="Output the manifest JSON schema",
        description="Output the manifest JSON schema for agent consumption",
    )

    # Manifest subcommand (with nested subcommands)
    manifest_parser = subparsers.add_parser(
        "manifest",
        help="Manifest management commands",
        description="Commands for creating and managing MAID manifests",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    manifest_subparsers = manifest_parser.add_subparsers(
        dest="manifest_command", help="Manifest commands"
    )

    # manifest create subcommand
    manifest_create_parser = manifest_subparsers.add_parser(
        "create",
        help="Create a new manifest for a file",
        description="""Create a new MAID manifest for a file.

Automatically handles:
- Task numbering (finds next available task number)
- Snapshot supersession (unfreezes snapshotted files)
- File mode detection (creatableFiles vs editableFiles)
- Validation command generation""",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    manifest_create_parser.add_argument(
        "file_path",
        help="Path to the file this manifest describes",
    )
    manifest_create_parser.add_argument(
        "--goal",
        required=True,
        help="Concise goal description for the manifest",
    )
    manifest_create_parser.add_argument(
        "--artifacts",
        help='JSON array of artifact definitions (e.g., \'[{"type": "function", "name": "foo"}]\')',
    )
    manifest_create_parser.add_argument(
        "--task-type",
        choices=["create", "edit", "refactor"],
        help="Task type (default: auto-detect based on file existence)",
    )
    manifest_create_parser.add_argument(
        "--force-supersede",
        help="Force supersede a specific manifest (for non-snapshots)",
    )
    manifest_create_parser.add_argument(
        "--test-file",
        help="Path to test file for validationCommand (default: auto-generated)",
    )
    manifest_create_parser.add_argument(
        "--readonly-files",
        help="Comma-separated list of readonly dependencies",
    )
    manifest_create_parser.add_argument(
        "--output-dir",
        default="manifests",
        help="Directory to write manifest (default: manifests)",
    )
    manifest_create_parser.add_argument(
        "--task-number",
        type=int,
        help="Force specific task number (default: auto-detect next available)",
    )
    manifest_create_parser.add_argument(
        "--json",
        action="store_true",
        help="Output created manifest as JSON (for agent consumption)",
    )
    manifest_create_parser.add_argument(
        "--quiet",
        "-q",
        action="store_true",
        help="Suppress informational messages",
    )
    manifest_create_parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Print manifest without writing to file",
    )
    manifest_create_parser.add_argument(
        "--delete",
        action="store_true",
        help="Create a deletion manifest with status: absent (supersedes all active manifests for the file)",
    )
    manifest_create_parser.add_argument(
        "--rename-to",
        help="New file path for rename/move operations (supersedes all active manifests for the source file)",
    )

    # Files subcommand
    files_parser = subparsers.add_parser(
        "files",
        help="Show file-level tracking status without full validation",
        description="Show which files are UNDECLARED, REGISTERED, or TRACKED in manifests",
    )
    files_parser.add_argument(
        "--manifest-dir",
        default="manifests",
        help="Directory containing manifests (default: manifests)",
    )
    files_parser.add_argument(
        "--issues-only",
        action="store_true",
        help="Only show undeclared and registered files (exclude tracked)",
    )
    files_parser.add_argument(
        "--status",
        choices=["undeclared", "registered", "tracked", "private_impl"],
        help="Filter by status (undeclared, registered, tracked, private_impl)",
    )
    files_parser.add_argument(
        "--quiet",
        "-q",
        action="store_true",
        help="Machine-readable output (no decorative elements)",
    )
    files_parser.add_argument(
        "--json",
        action="store_true",
        help="Output as JSON",
    )
    files_parser.add_argument(
        "--hide-private",
        action="store_true",
        help="Hide private implementation files (files starting with _)",
    )

    # Graph subcommand (with nested subcommands)
    setup_graph_parser(subparsers)

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        sys.exit(1)

    if args.command == "validate":
        from maid_runner.cli.validate import run_validation

        # Check for mutual exclusivity
        if args.manifest_path and args.manifest_dir:
            parser.error(
                "Cannot specify both manifest_path and --manifest-dir. Use one or the other."
            )

        # Default to manifests directory if neither is provided
        manifest_dir = args.manifest_dir
        if not args.manifest_path and not args.manifest_dir:
            manifest_dir = "manifests"

        # When validating a directory, always use manifest chain
        # This is the expected behavior for directory validation
        # For single-file validation, respect the user's flag
        use_manifest_chain = args.use_manifest_chain
        if manifest_dir:
            # Directory validation: use chain by default
            # User can still force it off by explicitly passing the flag for single files
            use_manifest_chain = True

        # Handle coherence-only mode: skip standard validation
        if args.coherence_only:
            args.use_manifest_chain = use_manifest_chain
            args.manifest_dir = manifest_dir
            coherence_valid = handle_coherence_validation(args)
            sys.exit(0 if coherence_valid else 1)

        # Run standard validation
        run_validation(
            args.manifest_path,
            args.validation_mode,
            use_manifest_chain,
            args.quiet,
            manifest_dir,
            skip_file_tracking=False,
            watch=args.watch,
            watch_all=args.watch_all,
            timeout=args.timeout,
            verbose=args.verbose,
            skip_tests=args.skip_tests,
            use_cache=args.use_cache,
            json_output=args.json_output,
        )

        # Run coherence validation if requested (after standard validation)
        if args.coherence:
            args.use_manifest_chain = use_manifest_chain
            args.manifest_dir = manifest_dir
            coherence_valid = handle_coherence_validation(args)
            if not coherence_valid:
                sys.exit(1)
    elif args.command == "snapshot":
        from maid_runner.cli.snapshot import run_snapshot

        run_snapshot(args.file_path, args.output_dir, args.force, args.skip_test_stub)
    elif args.command == "snapshot-system":
        from maid_runner.cli.snapshot_system import run_snapshot_system

        run_snapshot_system(args.output, args.manifest_dir, args.quiet)
    elif args.command == "test":
        from maid_runner.cli.test import run_test

        run_test(
            args.manifest_dir,
            args.fail_fast,
            args.verbose,
            args.quiet,
            args.timeout,
            args.manifest,
            args.watch,
            args.watch_all,
        )
    elif args.command == "manifests":
        from maid_runner.cli.list_manifests import run_list_manifests

        run_list_manifests(
            args.file_path, args.manifest_dir, args.quiet, args.json_output
        )
    elif args.command == "init":
        from maid_runner.cli.init import run_init

        # Determine which tools to enable
        tools = []
        if args.all:
            tools = ["claude", "cursor", "windsurf", "generic"]
        else:
            if args.claude or (
                not args.cursor and not args.windsurf and not args.generic
            ):
                tools.append("claude")
            if args.cursor:
                tools.append("cursor")
            if args.windsurf:
                tools.append("windsurf")
            if args.generic:
                tools.append("generic")

        run_init(args.target_dir, tools, args.force, args.dry_run)
    elif args.command == "generate-stubs":
        from maid_runner.cli.snapshot import generate_test_stub
        import json
        from pathlib import Path

        # Load the manifest
        manifest_path = Path(args.manifest_path)
        if not manifest_path.exists():
            print(f"Error: Manifest not found: {args.manifest_path}", file=sys.stderr)
            sys.exit(1)

        try:
            with open(manifest_path, "r") as f:
                manifest_data = json.load(f)

            # Generate the stub
            stub_path = generate_test_stub(manifest_data, str(manifest_path))
            print(f"Test stub generated: {stub_path}")

        except json.JSONDecodeError as e:
            print(f"Error: Invalid JSON in manifest: {e}", file=sys.stderr)
            sys.exit(1)
        except KeyError as e:
            print(f"Error: Missing required field in manifest: {e}", file=sys.stderr)
            sys.exit(1)
        except FileNotFoundError as e:
            print(f"Error: File not found: {e}", file=sys.stderr)
            sys.exit(1)
        except PermissionError as e:
            print(f"Error: Permission denied: {e}", file=sys.stderr)
            sys.exit(1)
        except Exception as e:
            print(f"Error generating stub: {e}", file=sys.stderr)
            sys.exit(1)
    elif args.command == "howto":
        from maid_runner.cli.howto import run_howto

        run_howto(section=args.section)
    elif args.command == "schema":
        from maid_runner.cli.schema import run_schema

        run_schema()
    elif args.command == "files":
        from maid_runner.cli.files import run_files

        run_files(
            args.manifest_dir,
            args.issues_only,
            args.status,
            args.quiet,
            args.json,
            args.hide_private,
        )
    elif args.command == "manifest":
        if not args.manifest_command:
            manifest_parser.print_help()
            sys.exit(1)

        if args.manifest_command == "create":
            from maid_runner.cli.manifest_create import run_create_manifest

            run_create_manifest(
                file_path=args.file_path,
                goal=args.goal,
                artifacts=args.artifacts,
                task_type=args.task_type,
                force_supersede=args.force_supersede,
                test_file=args.test_file,
                readonly_files=args.readonly_files,
                output_dir=args.output_dir,
                task_number=args.task_number,
                json_output=args.json,
                quiet=args.quiet,
                dry_run=args.dry_run,
                delete=args.delete,
                rename_to=args.rename_to,
            )
        else:
            manifest_parser.print_help()
            sys.exit(1)
    elif args.command == "graph":
        from maid_runner.cli.graph import run_graph_command

        sys.exit(run_graph_command(args))
    else:
        parser.print_help()
        sys.exit(1)


if __name__ == "__main__":
    main()
