"""CLI entry point using Typer."""

from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import Optional

import typer

from opencypher_compliance import __version__

app = typer.Typer(
    name="opencypher-compliance",
    help="Measure graph database compliance against the openCypher 9 specification.",
)


def _run_single(
    config_dict: dict | None,
    *,
    config_path: str | None = None,
    output_dir: str,
    format: str,
    catalog_dir: str | None,
    tag_list: list[str] | None,
    type_list: list[str] | None,
    verbose: bool,
    quiet: bool,
    fail_fast: bool,
    cli_overrides: dict,
) -> bool:
    """Run compliance tests for a single database config. Returns True on success."""
    from opencypher_compliance.runner import run_compliance
    from opencypher_compliance.output import format_json, format_console, write_output

    try:
        results = run_compliance(
            config=config_dict,
            config_path=config_path,
            catalog_dir=catalog_dir,
            tags=tag_list,
            types=type_list,
            verbose=verbose and not quiet,
            fail_fast=fail_fast,
            cli_overrides=cli_overrides,
        )
    except Exception as e:
        typer.echo(f"Error: {e}", err=True)
        return False

    if quiet:
        typer.echo(format_json(results))
    else:
        files = write_output(results, output_dir=output_dir, format=format)
        if not verbose:
            typer.echo(format_console(results), err=True)
        typer.echo("\nOutput written to:", err=True)
        for f in files:
            typer.echo(f"  {f}", err=True)
    return True


@app.command()
def run(
    config: Optional[Path] = typer.Option(None, "--config", "-c", help="Path to config YAML with a databases list"),
    adapter: Optional[str] = typer.Option(None, "--adapter", "-a", help="Adapter name"),
    host: Optional[str] = typer.Option(None, "--host", help="Database host"),
    port: Optional[int] = typer.Option(None, "--port", help="Database port"),
    username: Optional[str] = typer.Option(None, "--username", "-u", help="Username"),
    password: Optional[str] = typer.Option(None, "--password", "-p", help="Password"),
    output_dir: str = typer.Option("./results", "--output", "-o", help="Output directory"),
    format: str = typer.Option("both", "--format", "-f", help="Output format: json, csv, or both"),
    catalog_dir: Optional[str] = typer.Option(None, "--catalog-dir", help="Path to test catalog directory"),
    tags: Optional[str] = typer.Option(None, "--tags", help="Filter by tags (comma-separated)"),
    types: Optional[str] = typer.Option(None, "--types", help="Filter by element type (comma-separated)"),
    timeout: Optional[int] = typer.Option(None, "--timeout", help="Per-query timeout in seconds"),
    verbose: bool = typer.Option(False, "--verbose", "-v", help="Show each test as it runs"),
    quiet: bool = typer.Option(False, "--quiet", "-q", help="JSON to stdout (single database via inline flags only)"),
    fail_fast: bool = typer.Option(False, "--fail-fast", help="Stop on first failure"),
    report_flag: bool = typer.Option(False, "--report", "-r", help="Generate and open an HTML report after running"),
):
    """Execute compliance tests against one or more databases.

    Use -c to provide a config file with a databases list. For quick
    single-database runs, use inline flags (-a, --host, etc.) instead.
    """
    tag_list = [t.strip() for t in tags.split(",")] if tags else None
    type_list = [t.strip() for t in types.split(",")] if types else None

    # Default to config.yaml if no config file or inline flags provided
    if config is None and adapter is None:
        default_config = Path("config.yaml")
        if default_config.exists():
            config = default_config
        else:
            typer.echo(
                "No config file or adapter specified.\n"
                "Provide a config.yaml file or use inline flags (e.g. -a bolt --host localhost).",
                err=True,
            )
            raise typer.Exit(code=1)

    if config:
        # Config file path: always use load_multi_config, iterate all databases
        from opencypher_compliance.config import load_multi_config

        if quiet:
            typer.echo("--quiet is not supported with config files; use inline flags instead", err=True)
            raise typer.Exit(code=1)

        try:
            multi = load_multi_config(str(config))
        except Exception as e:
            typer.echo(f"Error: {e}", err=True)
            raise typer.Exit(code=1)

        cli_overrides = {}
        if timeout:
            cli_overrides["timeout"] = timeout

        passed_count = 0
        failed_count = 0

        for cfg in multi:
            db_name = cfg["database"].get("name", "unknown")
            if len(multi) > 1:
                typer.echo(f"\n{'='*60}", err=True)
                typer.echo(f"Running: {db_name}", err=True)
                typer.echo(f"{'='*60}", err=True)

            ok = _run_single(
                cfg,
                output_dir=output_dir,
                format=format,
                catalog_dir=catalog_dir,
                tag_list=tag_list,
                type_list=type_list,
                verbose=verbose,
                quiet=False,
                fail_fast=fail_fast,
                cli_overrides=cli_overrides,
            )
            if ok:
                passed_count += 1
            else:
                failed_count += 1

        if len(multi) > 1:
            typer.echo(f"\n{'='*60}", err=True)
            typer.echo(
                f"Completed: {passed_count} succeeded, {failed_count} failed "
                f"out of {len(multi)} databases",
                err=True,
            )

        if failed_count > 0:
            raise typer.Exit(code=1)

    else:
        # Inline flags / env vars path (single database)
        cli_overrides = {}
        if adapter:
            cli_overrides["adapter"] = adapter
        if host:
            cli_overrides["host"] = host
        if port:
            cli_overrides["port"] = port
        if username:
            cli_overrides["username"] = username
        if password:
            cli_overrides["password"] = password
        if timeout:
            cli_overrides["timeout"] = timeout

        ok = _run_single(
            None,
            output_dir=output_dir,
            format=format,
            catalog_dir=catalog_dir,
            tag_list=tag_list,
            type_list=type_list,
            verbose=verbose,
            quiet=quiet,
            fail_fast=fail_fast,
            cli_overrides=cli_overrides,
        )
        if not ok:
            raise typer.Exit(code=1)

    if report_flag:
        import webbrowser
        from opencypher_compliance.report import load_results_from_dir, write_report

        results = load_results_from_dir(output_dir)
        if results:
            report_path = write_report(results, output_dir=output_dir)
            typer.echo(f"Report written to {report_path}")
            webbrowser.open(report_path.resolve().as_uri())
        else:
            typer.echo("No result files found to generate report", err=True)


@app.command("list")
def list_tests(
    catalog_dir: Optional[str] = typer.Option(None, "--catalog-dir", help="Path to test catalog directory"),
    types: Optional[str] = typer.Option(None, "--types", help="Filter by element type (comma-separated)"),
    tags: Optional[str] = typer.Option(None, "--tags", help="Filter by tags (comma-separated)"),
):
    """List all tests in the catalog without executing them."""
    from opencypher_compliance.catalog import load_catalog

    tests = load_catalog(catalog_dir)

    if types:
        type_list = [t.strip() for t in types.split(",")]
        tests = [t for t in tests if t["type"] in type_list]

    if tags:
        tag_list = [t.strip() for t in tags.split(",")]
        tests = [t for t in tests if any(tag in t.get("tags", []) for tag in tag_list)]

    # Count by type
    type_counts: dict[str, int] = {}
    for t in tests:
        type_counts[t["type"]] = type_counts.get(t["type"], 0) + 1

    typer.echo(f"Catalog: {len(tests)} tests")
    for type_name, count in sorted(type_counts.items()):
        typer.echo(f"  {type_name}: {count}")
    typer.echo("")

    for t in tests:
        desc = t.get("description", "")
        line = f"  [{t['type']}] {t['name']}"
        if desc:
            line += f" — {desc}"
        typer.echo(line)


@app.command()
def validate(
    catalog_dir: Optional[str] = typer.Option(None, "--catalog-dir", help="Path to test catalog directory"),
):
    """Validate the test catalog YAML files for correctness."""
    from opencypher_compliance.catalog import load_catalog, validate_catalog

    try:
        tests = load_catalog(catalog_dir)
    except Exception as e:
        typer.echo(f"Error loading catalog: {e}", err=True)
        raise typer.Exit(code=1)

    errors = validate_catalog(tests)

    if errors:
        typer.echo(f"Validation failed with {len(errors)} error(s):", err=True)
        for err in errors:
            typer.echo(f"  - {err}", err=True)
        raise typer.Exit(code=1)

    typer.echo(f"Catalog valid: {len(tests)} tests across {len(set(t['type'] for t in tests))} types")


@app.command()
def report(
    input_dir: str = typer.Option("./results", "--input", "-i", help="Directory containing JSON result files"),
    output_dir: str = typer.Option("./results", "--output", "-o", help="Directory to write the HTML report"),
    filename: str = typer.Option("report.html", "--filename", help="Output filename"),
    no_open: bool = typer.Option(False, "--no-open", help="Do not open the report in a browser"),
):
    """Generate a self-contained HTML report from JSON result files."""
    from opencypher_compliance.report import load_results_from_dir, write_report

    results = load_results_from_dir(input_dir)
    if not results:
        typer.echo(f"No valid JSON result files found in {input_dir}", err=True)
        raise typer.Exit(code=1)

    report_path = write_report(results, output_dir=output_dir, filename=filename)
    typer.echo(f"Report written to {report_path}")

    if not no_open:
        import webbrowser
        webbrowser.open(report_path.resolve().as_uri())


def version_callback(value: bool):
    if value:
        typer.echo(f"opencypher-compliance v{__version__}")
        raise typer.Exit()


@app.callback()
def main(
    version: bool = typer.Option(False, "--version", callback=version_callback, is_eager=True),
):
    """Measure graph database compliance against the openCypher 9 specification."""
    pass
