"""MergeGuide CLI entry point."""

from __future__ import annotations

import sys
from pathlib import Path

import click
from rich.console import Console
from rich.table import Table

from mergeguide.engine.evaluator import PolicyEngine
from mergeguide.engine.reporter import ReportFormat
from mergeguide.policies.defaults import get_default_rules
from mergeguide.policies.loader import PolicyLoader

console = Console()

# Source file extensions to check
SOURCE_EXTENSIONS = [
    "*.py", "*.js", "*.ts", "*.tsx", "*.jsx",
    "*.java", "*.go", "*.rs", "*.rb", "*.php",
    "*.c", "*.cpp", "*.h", "*.hpp",
    "*.yaml", "*.yml", "*.json",
]


@click.group()
@click.version_option()
def main() -> None:
    """MergeGuide - Developer-transparent governance layer."""
    pass


@main.command()
@click.argument("paths", nargs=-1, type=click.Path(exists=True))
@click.option(
    "--format",
    "-f",
    "output_format",
    type=click.Choice(["text", "json", "sarif", "markdown"]),
    default="text",
    help="Output format",
)
@click.option(
    "--output",
    "-o",
    type=click.Path(),
    help="Write output to file",
)
@click.option(
    "--policy",
    "-p",
    multiple=True,
    type=click.Path(exists=True),
    help="Additional policy file(s) to use",
)
@click.option(
    "--no-defaults",
    is_flag=True,
    help="Don't use default policies",
)
@click.option(
    "--fail-on-warning",
    is_flag=True,
    help="Exit with error if warnings are found",
)
@click.option(
    "--staged",
    is_flag=True,
    help="Check only git-staged files (for pre-commit hooks)",
)
@click.option(
    "--hook-mode",
    is_flag=True,
    help="Optimized output for git hooks (concise, no spinner)",
)
def check(
    paths: tuple[str, ...],
    output_format: str,
    output: str | None,
    policy: tuple[str, ...],
    no_defaults: bool,
    fail_on_warning: bool,
    staged: bool,
    hook_mode: bool,
) -> None:
    """Check files against policies.

    PATHS: Files or directories to check. Reads from stdin if not provided.
    """
    # Initialize engine
    rules = [] if no_defaults else get_default_rules()
    engine = PolicyEngine(rules=rules)

    # Load additional policies
    loader = PolicyLoader()
    for policy_path in policy:
        try:
            policy_file = loader.load_file(policy_path)
            additional_rules = loader._policy_file_to_rules(policy_file)
            engine.add_rules(additional_rules)
        except Exception as e:
            console.print(f"[red]Error loading policy {policy_path}: {e}[/red]")
            sys.exit(1)

    # Handle --staged flag: get staged files from git
    if staged:
        try:
            from mergeguide.hooks.git_utils import get_staged_files, is_git_repo

            if not is_git_repo():
                console.print("[red]Error: --staged can only be used in a git repository[/red]")
                sys.exit(1)

            staged_files = get_staged_files()
            if not staged_files:
                if hook_mode:
                    print("MergeGuide: No staged files to check")
                else:
                    console.print("[green]No staged files to check[/green]")
                sys.exit(0)

            # Filter to source files only
            files_to_check = [
                f for f in staged_files
                if f.suffix in {".py", ".js", ".ts", ".tsx", ".jsx", ".java", ".go",
                               ".rs", ".rb", ".php", ".c", ".cpp", ".h", ".hpp",
                               ".yaml", ".yml", ".json"}
            ]

            if not files_to_check:
                if hook_mode:
                    print("MergeGuide: No source files staged")
                else:
                    console.print("[green]No source files staged[/green]")
                sys.exit(0)

        except Exception as e:
            console.print(f"[red]Error getting staged files: {e}[/red]")
            sys.exit(1)
    else:
        # Collect files to check from paths
        files_to_check: list[Path] = []
        for path_str in paths:
            path = Path(path_str)
            if path.is_file():
                files_to_check.append(path)
            elif path.is_dir():
                for ext in SOURCE_EXTENSIONS:
                    files_to_check.extend(path.rglob(ext))

    if not files_to_check and not paths and not staged:
        # Read from stdin
        console.print("[dim]Reading from stdin...[/dim]")
        content = sys.stdin.read()
        output_result = engine.evaluate_content(content)
        _output_results(output_result, output_format, output, hook_mode)
        _exit_with_code(output_result.passed, fail_on_warning, output_result.violations)
        return

    if not files_to_check:
        if hook_mode:
            print("MergeGuide: No files to check")
        else:
            console.print("[yellow]No files to check[/yellow]")
        return

    # Check all files
    all_violations = []
    all_files_checked = []

    if hook_mode:
        # Hook mode: no spinner, simple output
        for file_path in files_to_check:
            try:
                content = file_path.read_text()
                result = engine.evaluate_file(str(file_path), content)
                all_violations.extend(result.violations)
                all_files_checked.append(str(file_path))
            except Exception as e:
                print(f"Error reading {file_path}: {e}", file=sys.stderr)
    else:
        # Interactive mode: use spinner
        with console.status("[bold blue]Checking files...") as status:
            for file_path in files_to_check:
                try:
                    status.update(f"[bold blue]Checking {file_path}...")
                    content = file_path.read_text()
                    result = engine.evaluate_file(str(file_path), content)
                    all_violations.extend(result.violations)
                    all_files_checked.append(str(file_path))
                except Exception as e:
                    console.print(f"[red]Error reading {file_path}: {e}[/red]")

    # Create combined output
    from mergeguide.engine.reporter import EvaluationReport

    report = EvaluationReport.create(
        evaluation_id="cli-check",
        violations=all_violations,
        files_checked=all_files_checked,
        duration_ms=0,
    )

    # Output results
    if hook_mode:
        _output_hook_results(report)
    else:
        _output_results_from_report(report, output_format, output)

    # Exit with appropriate code
    has_errors = any(v.severity == "error" for v in all_violations)
    has_warnings = any(v.severity == "warning" for v in all_violations)

    if has_errors:
        sys.exit(1)
    elif has_warnings and fail_on_warning:
        sys.exit(1)


@main.command()
@click.option(
    "--global",
    "global_config",
    is_flag=True,
    help="Initialize globally in ~/.mergeguide",
)
def init(global_config: bool) -> None:
    """Initialize MergeGuide in current repository."""
    if global_config:
        config_dir = Path.home() / ".mergeguide"
    else:
        config_dir = Path(".mergeguide")

    config_dir.mkdir(parents=True, exist_ok=True)
    policies_dir = config_dir / "policies"
    policies_dir.mkdir(exist_ok=True)

    # Create sample policy file
    sample_policy = policies_dir / "custom.yaml"
    if not sample_policy.exists():
        sample_policy.write_text("""# Custom MergeGuide Policies
version: "1.0"

metadata:
  name: "Custom Policies"
  description: "Project-specific policy rules"

policies:
  - id: "example-rule"
    name: "Example Rule"
    description: "An example custom rule"
    severity: warning
    languages: ["*"]
    enabled: false  # Set to true to enable
    rules:
      - pattern: "example_pattern"
        message: "This is an example violation"
        remediation: "Replace with the correct pattern"
""")

    console.print(f"[green]Initialized MergeGuide in {config_dir}[/green]")
    console.print(f"  - Created policies directory: {policies_dir}")
    console.print(f"  - Created sample policy: {sample_policy}")
    console.print("\nRun [bold]mergeguide check <files>[/bold] to scan your code")


@main.command()
def policies() -> None:
    """List available policies."""
    rules = get_default_rules()

    table = Table(title="MergeGuide Policies")
    table.add_column("ID", style="cyan")
    table.add_column("Name", style="green")
    table.add_column("Severity", style="yellow")
    table.add_column("Languages")

    # Deduplicate by policy ID (rules may have suffixes)
    seen = set()
    for rule in rules:
        base_id = rule.id.rsplit("-", 1)[0] if "-" in rule.id else rule.id
        if base_id not in seen:
            seen.add(base_id)
            severity_style = {
                "error": "red",
                "warning": "yellow",
                "info": "blue",
            }.get(rule.severity.value, "white")

            table.add_row(
                base_id,
                rule.name,
                f"[{severity_style}]{rule.severity.value}[/{severity_style}]",
                ", ".join(rule.languages) if rule.languages != ["*"] else "all",
            )

    console.print(table)


def _output_results(
    result, output_format: str, output_path: str | None, hook_mode: bool = False
) -> None:
    """Output evaluation results."""
    if hook_mode:
        _output_hook_results(result.report)
        return

    from mergeguide.engine.reporter import Reporter

    reporter = Reporter()
    format_map = {
        "text": ReportFormat.TEXT,
        "json": ReportFormat.JSON,
        "sarif": ReportFormat.SARIF,
        "markdown": ReportFormat.MARKDOWN,
    }

    formatted = reporter.format(result.report, format_map[output_format])

    if output_path:
        Path(output_path).write_text(formatted)
        console.print(f"[green]Output written to {output_path}[/green]")
    else:
        if output_format == "text":
            _print_text_results(result)
        else:
            console.print(formatted)


def _output_results_from_report(report, output_format: str, output_path: str | None) -> None:
    """Output results from a report."""
    from mergeguide.engine.reporter import Reporter

    reporter = Reporter()
    format_map = {
        "text": ReportFormat.TEXT,
        "json": ReportFormat.JSON,
        "sarif": ReportFormat.SARIF,
        "markdown": ReportFormat.MARKDOWN,
    }

    formatted = reporter.format(report, format_map[output_format])

    if output_path:
        Path(output_path).write_text(formatted)
        console.print(f"[green]Output written to {output_path}[/green]")
    else:
        if output_format == "text":
            _print_text_report(report)
        else:
            console.print(formatted)


def _print_text_results(result) -> None:
    """Print results in human-readable format."""
    if result.passed:
        console.print(f"\n[green]PASSED[/green] - {len(result.files_checked)} file(s) checked")
    else:
        console.print(
            f"\n[red]FAILED[/red] - {len(result.violations)} violation(s) in "
            f"{len(result.files_checked)} file(s)"
        )

    for violation in result.violations:
        severity_style = {"error": "red", "warning": "yellow", "info": "blue"}.get(
            violation.severity, "white"
        )
        console.print(
            f"\n[{severity_style}]{violation.severity.upper()}[/{severity_style}] "
            f"{violation.file_path}:{violation.line_no}:{violation.column_start}"
        )
        console.print(f"  {violation.rule_name}: {violation.message}")
        console.print(f"  [dim]> {violation.line_content.strip()}[/dim]")
        if violation.remediation:
            console.print(f"  [cyan]Remediation: {violation.remediation}[/cyan]")


def _print_text_report(report) -> None:
    """Print report in human-readable format."""
    if report.summary.passed:
        console.print(
            f"\n[green]PASSED[/green] - {report.summary.total_files} file(s) checked"
        )
    else:
        console.print(
            f"\n[red]FAILED[/red] - {report.summary.total_violations} violation(s) in "
            f"{report.summary.files_with_violations} file(s)"
        )

    for violation in report.violations:
        severity_style = {"error": "red", "warning": "yellow", "info": "blue"}.get(
            violation.severity, "white"
        )
        console.print(
            f"\n[{severity_style}]{violation.severity.upper()}[/{severity_style}] "
            f"{violation.file_path}:{violation.line_no}:{violation.column_start}"
        )
        console.print(f"  {violation.rule_name}: {violation.message}")
        console.print(f"  [dim]> {violation.line_content.strip()}[/dim]")
        if violation.remediation:
            console.print(f"  [cyan]Remediation: {violation.remediation}[/cyan]")


def _exit_with_code(passed: bool, fail_on_warning: bool, violations: list) -> None:
    """Exit with appropriate code based on results."""
    has_errors = any(v.severity == "error" for v in violations)
    has_warnings = any(v.severity == "warning" for v in violations)

    if has_errors:
        sys.exit(1)
    elif has_warnings and fail_on_warning:
        sys.exit(1)
    else:
        sys.exit(0)


def _output_hook_results(report) -> None:
    """Output results in hook-friendly format (no Rich, plain text)."""
    errors = sum(1 for v in report.violations if v.severity == "error")
    warnings = sum(1 for v in report.violations if v.severity == "warning")
    infos = sum(1 for v in report.violations if v.severity == "info")

    if report.summary.passed:
        print(f"MergeGuide: PASSED - {report.summary.total_files} file(s) checked")
        return

    print(f"\nMergeGuide: FAILED - {report.summary.total_violations} violation(s) found")
    print(f"  Errors: {errors}, Warnings: {warnings}, Info: {infos}")
    print()

    for violation in report.violations:
        severity_icon = {"error": "ERROR", "warning": "WARN", "info": "INFO"}.get(
            violation.severity, "????"
        )
        print(f"[{severity_icon}] {violation.file_path}:{violation.line_no}")
        print(f"  {violation.rule_name}: {violation.message}")
        if violation.line_content:
            print(f"  > {violation.line_content.strip()}")
        if violation.remediation:
            print(f"  Fix: {violation.remediation}")
        print()


# ============================================================================
# Hooks command group
# ============================================================================


@main.group()
def hooks() -> None:
    """Manage Git hooks for local policy enforcement."""
    pass


@hooks.command("install")
@click.option("--force", is_flag=True, help="Overwrite existing hooks")
@click.option(
    "--hook-type",
    type=click.Choice(["all", "pre-commit", "pre-push"]),
    default="all",
    help="Type of hook to install",
)
def hooks_install(force: bool, hook_type: str) -> None:
    """Install MergeGuide git hooks."""
    from mergeguide.hooks.installer import (
        HookExistsError,
        HookInstaller,
        NotInRepoError,
    )

    try:
        installer = HookInstaller()
        installed = installer.install(hook_type, force=force)

        if installed:
            console.print(f"[green]Installed hooks:[/green] {', '.join(installed)}")
            console.print("\nHooks will run automatically on commit/push.")
            console.print("Bypass with --no-verify flag if needed.")
        else:
            console.print("[yellow]No hooks installed[/yellow]")

    except NotInRepoError:
        console.print("[red]Error: Not in a git repository[/red]")
        console.print("Run this command from within a git repository.")
        sys.exit(1)
    except HookExistsError as e:
        console.print(f"[red]Error: {e}[/red]")
        console.print("Use --force to overwrite existing hooks.")
        sys.exit(1)
    except Exception as e:
        console.print(f"[red]Error installing hooks: {e}[/red]")
        sys.exit(1)


@hooks.command("uninstall")
@click.option(
    "--hook-type",
    type=click.Choice(["all", "pre-commit", "pre-push"]),
    default="all",
    help="Type of hook to uninstall",
)
def hooks_uninstall(hook_type: str) -> None:
    """Remove MergeGuide git hooks."""
    from mergeguide.hooks.installer import HookInstaller, NotInRepoError

    try:
        installer = HookInstaller()
        uninstalled = installer.uninstall(hook_type)

        if uninstalled:
            console.print(f"[green]Uninstalled hooks:[/green] {', '.join(uninstalled)}")
        else:
            console.print("[yellow]No MergeGuide hooks found to uninstall[/yellow]")

    except NotInRepoError:
        console.print("[red]Error: Not in a git repository[/red]")
        sys.exit(1)
    except Exception as e:
        console.print(f"[red]Error uninstalling hooks: {e}[/red]")
        sys.exit(1)


@hooks.command("status")
def hooks_status() -> None:
    """Show status of installed hooks."""
    from mergeguide.hooks.installer import HookInstaller, NotInRepoError

    try:
        installer = HookInstaller()
        status = installer.status()

        table = Table(title="MergeGuide Hook Status")
        table.add_column("Hook", style="cyan")
        table.add_column("Status")

        for hook, hook_status in status.items():
            if hook_status == "installed":
                status_display = "[green]Installed[/green]"
            elif hook_status == "other":
                status_display = "[yellow]Other hook exists[/yellow]"
            else:
                status_display = "[dim]Not installed[/dim]"

            table.add_row(hook, status_display)

        console.print(table)

        # Show helpful commands
        not_installed = [h for h, s in status.items() if s == "not_installed"]
        if not_installed:
            console.print(f"\nTo install: [bold]mergeguide hooks install[/bold]")

    except NotInRepoError:
        console.print("[red]Error: Not in a git repository[/red]")
        sys.exit(1)
    except Exception as e:
        console.print(f"[red]Error checking hook status: {e}[/red]")
        sys.exit(1)


if __name__ == "__main__":
    main()
