"""CLI entry point using Click — defines all commands and options."""

from __future__ import annotations

import logging
import os
import sys
from pathlib import Path
from typing import Optional

import click

from . import __version__

logger = logging.getLogger("oncecheck")


def _setup_logging(verbose: bool = False) -> None:
    """Configure logging for the CLI."""
    level = logging.DEBUG if verbose else logging.WARNING
    logging.basicConfig(
        level=level,
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
        datefmt="%H:%M:%S",
    )


@click.group(invoke_without_command=True)
@click.version_option(version=__version__, prog_name="Oncecheck")
@click.pass_context
def cli(ctx):
    """Oncecheck — scan iOS, Android, and Web projects for compliance risks."""
    if ctx.invoked_subcommand is None:
        _interactive_flow()


def _interactive_flow() -> None:
    """Full interactive welcome screen + menu-driven scan flow."""
    from .auth.api import check_scan_quota, record_scan
    from .auth.login import ensure_authenticated, login_flow
    from .auth.token_store import load_token
    from .engine.runner import run_scan
    from .scanners.detector import Platform, DetectionResult, detect_platforms
    from .engine.reporter import export_json, export_text, export_sarif
    from .ui.rich_console import (
        console,
        print_welcome,
        prompt_main_menu,
        prompt_platform,
        prompt_project_path,
        prompt_post_scan,
        prompt_export_format,
        prompt_export_location,
        print_header,
        print_scan_result,
        print_footer,
    )
    from .ui.interactive import show_interactive

    print_welcome()

    while True:
        choice = prompt_main_menu()

        if choice == "scan":
            # --- Auth gate ---
            try:
                token_data = ensure_authenticated()
            except SystemExit:
                raise
            except (OSError, ConnectionError, TimeoutError) as exc:
                console.print(f"\n  [bold #ef4444]Auth error:[/bold #ef4444] {exc}\n")
                import readchar
                console.print("  [dim]Press any key to continue...[/dim]")
                readchar.readkey()
                continue

            # --- Quota gate (server-side) ---
            try:
                quota = check_scan_quota(token_data["access_token"])
            except PermissionError:
                console.print("\n  [bold #ef4444]Session expired.[/bold #ef4444] Select Login to re-authenticate.\n")
                import readchar
                console.print("  [dim]Press any key to continue...[/dim]")
                readchar.readkey()
                continue
            except ConnectionError as exc:
                console.print(f"\n  [bold #ef4444]Server error:[/bold #ef4444] {exc}\n")
                import readchar
                console.print("  [dim]Press any key to continue...[/dim]")
                readchar.readkey()
                continue

            if not quota["allowed"]:
                console.print(f"\n  [bold #eab308]Scan limit reached[/bold #eab308] ({quota['used']}/{quota['limit']} scans today)")
                console.print("  Upgrade to [bold]Team[/bold] for unlimited scans: [#eeef20]https://www.oncecheck.com/dashboard/upgrade[/#eeef20]\n")
                import readchar
                console.print("  [dim]Press any key to continue...[/dim]")
                readchar.readkey()
                continue

            user_plan = quota.get("plan", "starter")

            # --- Scan flow ---
            project_path_str = prompt_project_path()
            project_path = Path(project_path_str).resolve()

            platform_choice = prompt_platform()
            console.clear()

            if platform_choice == "auto":
                detections = detect_platforms(project_path)
                if not detections:
                    console.print("  [bold #ef4444]Could not detect project type.[/bold #ef4444]")
                    console.print("  [dim]Try selecting the platform manually.[/dim]\n")
                    import readchar
                    readchar.readkey()
                    continue
            else:
                detections = [
                    DetectionResult(
                        platform=Platform(platform_choice),
                        project_root=project_path,
                        project_type="Manual",
                        confidence=1.0,
                    )
                ]

            results = []
            for detection in detections:
                console.print(f"  [dim]Scanning {detection.platform.value.upper()} ({detection.project_type})...[/dim]")
                result = run_scan(detection, user_plan=user_plan)
                results.append(result)

            # Record the completed scan (increments counter)
            try:
                record_scan(token_data["access_token"])
            except (PermissionError, ConnectionError):
                pass  # Don't block results if recording fails

            console.clear()
            print_header()
            for result in results:
                print_scan_result(result)
            print_footer(results)

            # Post-scan menu
            if any(r.findings for r in results):
                import readchar
                console.print("  [dim]Press any key to continue...[/dim]")
                readchar.readkey()
                action = prompt_post_scan(is_team=(user_plan == "team"))
                if action == "browse":
                    show_interactive(results)
                elif action == "export":
                    fmt = prompt_export_format()
                    save_dir = prompt_export_location()
                    ext = "txt" if fmt == "text" else fmt
                    filename = f"oncecheck-results.{ext}"
                    output_path = str(Path(save_dir) / filename)
                    try:
                        if fmt == "json":
                            export_json(results, output_path)
                        elif fmt == "text":
                            Path(output_path).write_text(export_text(results))
                        elif fmt == "sarif":
                            export_sarif(results, output_path)
                        console.print(f"\n  [#22c55e]Exported to {output_path}[/#22c55e]\n")
                    except (ValueError, FileNotFoundError, OSError) as exc:
                        console.print(f"\n  [bold #ef4444]Export failed:[/bold #ef4444] {exc}\n")
                    readchar.readkey()
                elif action == "exit":
                    console.print("\n  [dim]Goodbye![/dim]\n")
                    break
            else:
                import readchar
                console.print("  [dim]Press any key to continue...[/dim]")
                readchar.readkey()

        elif choice == "login":
            login_flow()
            console.print()

        elif choice == "status":
            console.clear()
            token = load_token()
            if token:
                plan = token.get("plan", "Unknown")
                email = token.get("email", "Unknown")
                console.print(f"\n  [#22c55e]Authenticated[/#22c55e] \u2014 {email}")
                console.print(f"  Plan: [bold]{plan}[/bold]\n")
            else:
                console.print("\n  [#eab308]Not authenticated.[/#eab308] Select Login to sign in.\n")
            import readchar
            console.print("  [dim]Press any key to continue...[/dim]")
            readchar.readkey()

        elif choice == "exit":
            console.clear()
            console.print("\n  [dim]Goodbye![/dim]\n")
            break


@cli.command()
@click.argument("path", default=".", type=click.Path(exists=True))
@click.option(
    "--platform", "-p",
    type=click.Choice(["ios", "android", "web", "auto"], case_sensitive=False),
    default="auto",
    help="Force a specific platform instead of auto-detecting.",
)
@click.option(
    "--format", "-f", "output_format",
    type=click.Choice(["rich", "json", "text", "sarif"], case_sensitive=False),
    default="rich",
    help="Output format.",
)
@click.option("--output", "-o", type=click.Path(), default=None, help="Write results to file.")
@click.option("--fail-on", type=click.Choice(["FAIL", "WARN"], case_sensitive=False), default=None,
              help="Exit with code 2 if findings of this severity or higher exist.")
@click.option("--interactive", "-i", is_flag=True, help="Launch interactive findings browser.")
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging.")
def scan(
    path: str,
    platform: str,
    output_format: str,
    output: Optional[str],
    fail_on: Optional[str],
    interactive: bool,
    verbose: bool,
):
    """Scan a project directory for compliance issues."""
    from rich.console import Console
    from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn

    from .auth.api import check_scan_quota, record_scan
    from .auth.login import ensure_authenticated
    from .engine.config import ScanConfig
    from .engine.reporter import export_json, export_sarif, export_text
    from .engine.runner import run_scan, ScanResult
    from .scanners.detector import Platform, detect_platforms
    from .ui.rich_console import print_header, print_scan_result, print_footer
    from .ui.interactive import show_interactive

    _setup_logging(verbose)

    console = Console()
    project_path = Path(path).resolve()

    # --- Load project config ---
    config = ScanConfig(project_path)
    logger.info("Config loaded: %d ignored rules, %d severity overrides",
                len(config.ignored_rules), len(config.severity_overrides))

    # --- Auth gate ---
    try:
        token_data = ensure_authenticated()
    except SystemExit:
        raise
    except (OSError, ConnectionError, TimeoutError) as exc:
        console.print(f"[bold #ef4444]Auth error:[/bold #ef4444] {exc}")
        sys.exit(1)

    # --- Quota gate (server-side) ---
    try:
        quota = check_scan_quota(token_data["access_token"])
    except PermissionError:
        console.print("[bold #ef4444]Session expired.[/bold #ef4444] Run `oncecheck login` to re-authenticate.")
        sys.exit(1)
    except ConnectionError as exc:
        console.print(f"[bold #ef4444]Server error:[/bold #ef4444] {exc}")
        sys.exit(1)

    if not quota["allowed"]:
        console.print(f"\n  [bold #eab308]Scan limit reached[/bold #eab308] ({quota['used']}/{quota['limit']} scans today)")
        console.print("  Upgrade to [bold]Team[/bold] for unlimited scans: [#eeef20]https://www.oncecheck.com/dashboard/upgrade[/#eeef20]\n")
        sys.exit(1)

    # --- Plan-gated features ---
    user_plan = quota.get("plan", "starter")
    if user_plan != "team":
        if output_format == "sarif":
            console.print("[bold #eab308]SARIF export requires the Team plan.[/bold #eab308]")
            console.print("Upgrade at: [#eeef20]https://www.oncecheck.com/dashboard/upgrade[/#eeef20]")
            sys.exit(1)
        if output and output_format != "rich":
            console.print("[bold #eab308]File export (--output) requires the Team plan.[/bold #eab308]")
            console.print("Upgrade at: [#eeef20]https://www.oncecheck.com/dashboard/upgrade[/#eeef20]")
            sys.exit(1)

    # --- Detect platforms ---
    if platform == "auto":
        detections = detect_platforms(project_path)
        if not detections:
            console.print("[bold #ef4444]Could not detect project type.[/bold #ef4444]")
            console.print("Use --platform to specify manually.")
            sys.exit(1)
    else:
        from .scanners.detector import DetectionResult
        detections = [
            DetectionResult(
                platform=Platform(platform.lower()),
                project_root=project_path,
                project_type="Manual",
                confidence=1.0,
            )
        ]

    # --- Run scans with progress ---
    results: list[ScanResult] = []
    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        TimeElapsedColumn(),
        console=console,
        transient=True,
    ) as progress:
        for detection in detections:
            task_desc = f"Scanning {detection.platform.value.upper()} ({detection.project_type})"
            progress.add_task(task_desc, total=None)
            result = run_scan(detection, config=config, user_plan=user_plan)
            results.append(result)

    # --- Record the completed scan ---
    try:
        record_scan(token_data["access_token"])
    except (PermissionError, ConnectionError):
        pass  # Don't block results if recording fails

    # --- Resolve fail_on: CLI flag > config file ---
    effective_fail_on = fail_on or config.fail_on

    # --- Output ---
    if output_format == "rich":
        print_header()
        for result in results:
            print_scan_result(result)
        print_footer(results)
        # Show suppression summary
        total_suppressed = sum(r.suppressed_count for r in results)
        if total_suppressed:
            console.print(f"  [dim]{total_suppressed} finding(s) suppressed via .oncecheckignore[/dim]")
        # Show plan gating summary
        total_gated = sum(r.gated_count for r in results)
        if total_gated:
            console.print(f"  [dim]{total_gated} additional rule(s) available with Team plan[/dim]")
            console.print(f"  [dim]Upgrade at: https://www.oncecheck.com/dashboard/upgrade[/dim]\n")
    elif output_format == "json":
        text = export_json(results, output)
        if not output:
            console.print(text)
    elif output_format == "text":
        text = export_text(results)
        if output:
            Path(output).write_text(text)
        else:
            console.print(text)
    elif output_format == "sarif":
        text = export_sarif(results, output)
        if not output:
            console.print(text)

    if output:
        console.print(f"  [#22c55e]Results written to {output}[/#22c55e]")

    # --- Interactive mode ---
    if interactive:
        show_interactive(results)

    # --- Exit code ---
    _exit_code(results, effective_fail_on)


def _exit_code(results, fail_on: Optional[str]) -> None:
    """Set process exit code based on findings."""
    total_fail = sum(r.fail_count for r in results)
    total_warn = sum(r.warn_count for r in results)

    if fail_on == "FAIL" and total_fail > 0:
        sys.exit(2)
    elif fail_on == "WARN" and (total_fail > 0 or total_warn > 0):
        sys.exit(2)
    elif total_fail > 0:
        sys.exit(2)
    elif total_warn > 0:
        sys.exit(1)
    else:
        sys.exit(0)


@cli.command()
def login():
    """Authenticate with Oncecheck."""
    from .auth.login import login_flow
    login_flow()


@cli.command()
def logout():
    """Clear stored authentication credentials."""
    from .auth.login import logout
    logout()


@cli.command()
def status():
    """Show authentication and subscription status."""
    from rich.console import Console

    from .auth.token_store import load_token

    console = Console()
    token = load_token()
    if token:
        plan = token.get("plan", "Unknown")
        email = token.get("email", "Unknown")
        console.print(f"  [#22c55e]Authenticated[/#22c55e] \u2014 {email}")
        console.print(f"  Plan: [bold]{plan}[/bold]")
    else:
        console.print("  [#eab308]Not authenticated.[/#eab308] Run `oncecheck login` to sign in.")


@cli.command("completions")
@click.argument("shell", type=click.Choice(["bash", "zsh", "fish"]))
def completions(shell: str):
    """Generate shell completion script.

    \b
    Install completions:
      oncecheck completions bash >> ~/.bashrc
      oncecheck completions zsh >> ~/.zshrc
      oncecheck completions fish > ~/.config/fish/completions/oncecheck.fish
    """
    import subprocess

    env_var = "_ONCECHECK_COMPLETE"
    shell_map = {"bash": "bash_source", "zsh": "zsh_source", "fish": "fish_source"}

    result = subprocess.run(
        ["oncecheck"],
        env={**os.environ, env_var: shell_map[shell]},
        capture_output=True,
        text=True,
    )
    click.echo(result.stdout)


@cli.command("init")
@click.argument("path", default=".", type=click.Path(exists=True))
def init_config(path: str):
    """Generate a .oncecheckrc config and .oncecheckignore in the project."""
    from rich.console import Console

    console = Console()
    project_path = Path(path).resolve()
    rc_path = project_path / ".oncecheckrc"

    if rc_path.exists():
        console.print(f"  [#eab308]Config already exists:[/#eab308] {rc_path}")
    else:
        rc_path.write_text(
            "# Oncecheck configuration\n"
            "# https://oncecheck.com\n"
            "\n"
            "# Rules to suppress (same as .oncecheckignore)\n"
            "disabled_rules: []\n"
            "  # - IOS-SEC-001\n"
            "  # - WEB-OWASP-003\n"
            "\n"
            "# Override severity for specific rules\n"
            "severity_overrides: {}\n"
            "  # IOS-SEC-001: WARN\n"
            "  # WEB-A11Y-001: INFO\n"
            "\n"
            "# Exit code threshold (FAIL or WARN)\n"
            "# fail_on: FAIL\n"
        )
        console.print(f"  [#22c55e]Created config:[/#22c55e] {rc_path}")

    ignore_path = project_path / ".oncecheckignore"
    if ignore_path.exists():
        console.print(f"  [#eab308]Ignore file already exists:[/#eab308] {ignore_path}")
    else:
        ignore_path.write_text(
            "# Oncecheck — suppressed rules (one ID per line)\n"
            "# Lines starting with # are comments\n"
            "#\n"
            "# Example:\n"
            "# IOS-SEC-001\n"
            "# WEB-OWASP-003\n"
        )
        console.print(f"  [#22c55e]Created ignore file:[/#22c55e] {ignore_path}")
