"""CLI entry point for Sage Evaluator."""

from __future__ import annotations

import asyncio
import json
import logging
import logging.config
import sys
from importlib.resources import as_file, files
from pathlib import Path

import click

from sage_evaluator import __version__
from sage_evaluator.models import Severity, ValidationResult

logger = logging.getLogger(__name__)


def _configure_logging() -> None:
    """Load logging configuration from the packaged logging.conf file."""
    ref = files("sage_evaluator").joinpath("logging.conf")
    with as_file(ref) as conf_path:
        logging.config.fileConfig(conf_path, disable_existing_loggers=False)


@click.group()
@click.version_option(version=__version__, prog_name="sage-evaluator")
@click.option(
    "--config",
    "config_path",
    default=None,
    type=click.Path(exists=True),
    help="Path to main config.toml (overrides SAGE_CONFIG_PATH and default locations).",
)
@click.pass_context
def cli(ctx: click.Context, config_path: str | None) -> None:
    """Sage Evaluator - validate, benchmark, and optimize agent configurations."""
    _configure_logging()

    obj = ctx.ensure_object(dict)
    try:
        from sage.main_config import load_main_config, resolve_main_config_path

        resolved = resolve_main_config_path(config_path)
        obj["main_config"] = load_main_config(resolved)
    except Exception:
        logger.debug("Could not load main config (sage.main_config unavailable or no config found)")
        obj["main_config"] = None


def _get_main_config(ctx: click.Context):
    """Extract main config from click context."""
    obj = ctx.ensure_object(dict)
    return obj.get("main_config")


def _get_default_model(ctx: click.Context) -> str:
    """Extract the default model from main config, or raise if unavailable."""
    main_config = _get_main_config(ctx)
    if main_config and main_config.defaults and main_config.defaults.model:
        return main_config.defaults.model
    raise click.ClickException(
        "No default model found. Provide a config.toml with [defaults] model = '...' "
        "or use --config to point to one."
    )


# ── validate command ──────────────────────────────────────────────────────────


@cli.command("validate")
@click.argument("paths", nargs=-1, required=True, type=click.Path(exists=False))
@click.option(
    "--strict",
    is_flag=True,
    default=False,
    help="Treat warnings as errors; exit non-zero if any warnings are present.",
)
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["text", "json"], case_sensitive=False),
    default="text",
    show_default=True,
    help="Output format.",
)
def validate_command(paths: tuple[str, ...], strict: bool, output_format: str) -> None:
    """Validate one or more agent/skill configuration files.

    PATHS may be individual .md files or directories.  Directories are
    searched for an AGENTS.md file automatically.

    \b
    Exit codes:
      0  all files passed (or passed after --strict promotion)
      1  one or more files have ERROR-level issues
    """
    from sage_evaluator.validation.validator import AgentValidator

    logger.info("Validating %d path(s)", len(paths))
    validator = AgentValidator()
    results: list[ValidationResult] = []

    for raw_path in paths:
        p = Path(raw_path)
        if p.is_dir():
            candidate = p / "AGENTS.md"
            result = validator.validate(candidate)
            # Preserve the original directory path so output is readable.
            result = result.model_copy(update={"path": str(p)})
        else:
            result = validator.validate(p)
        results.append(result)

    if strict:
        results = _apply_strict(results)

    if output_format == "json":
        _output_json(results)
    else:
        _output_text(results)

    # Exit 1 if any result has errors.
    if any(not r.valid for r in results):
        sys.exit(1)


# ── Output helpers ────────────────────────────────────────────────────────────


def _apply_strict(results: list[ValidationResult]) -> list[ValidationResult]:
    """Promote WARNING issues to ERROR and recompute the valid flag."""
    promoted: list[ValidationResult] = []
    for result in results:
        new_issues = []
        for issue in result.issues:
            if issue.severity == Severity.WARNING:
                new_issues.append(issue.model_copy(update={"severity": Severity.ERROR}))
            else:
                new_issues.append(issue)
        valid = not any(i.severity == Severity.ERROR for i in new_issues)
        promoted.append(result.model_copy(update={"issues": new_issues, "valid": valid}))
    return promoted


def _output_json(results: list[ValidationResult]) -> None:
    """Print results as a JSON array to stdout."""
    payload = [r.model_dump() for r in results]
    click.echo(json.dumps(payload, indent=2))


def _output_text(results: list[ValidationResult]) -> None:
    """Render results as a Rich table to the terminal."""
    from sage_evaluator.reporting.terminal import print_validation_results

    print_validation_results(results)


# ── discover command ───────────────────────────────────────────────────────────


@cli.command("discover")
@click.option(
    "--account-name",
    envvar="AZURE_AI_ACCOUNT_NAME",
    required=True,
    help=(
        "Azure Cognitive Services account name.  "
        "Can also be set via the AZURE_AI_ACCOUNT_NAME environment variable."
    ),
    metavar="NAME",
)
@click.option(
    "--subscription",
    envvar="AZURE_SUBSCRIPTION_ID",
    default=None,
    help="Azure subscription ID.  Skips auto-discovery when combined with --resource-group.",
)
@click.option(
    "--resource-group",
    envvar="AZURE_RESOURCE_GROUP",
    default=None,
    help="Azure resource group name.  Skips auto-discovery when combined with --subscription.",
)
@click.option(
    "--include-pricing",
    is_flag=True,
    default=False,
    help="Enrich each discovered model with per-token pricing information.",
)
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["text", "json"], case_sensitive=False),
    default="text",
    show_default=True,
    help="Output format: 'text' renders a Rich table; 'json' emits newline-delimited JSON.",
)
def discover_command(
    account_name: str,
    subscription: str | None,
    resource_group: str | None,
    include_pricing: bool,
    output_format: str,
) -> None:
    """Discover models deployed in an Azure Cognitive Services account.

    Lists all model deployments using the Azure Management SDK.
    Subscription and resource group are auto-discovered from the account
    name unless explicitly provided.

    \b
    Examples:
        evaluate discover --account-name d-ue2-aicorepltfm-aisvcs
        evaluate discover --account-name $AZURE_AI_ACCOUNT_NAME --include-pricing --format json
        evaluate discover --account-name myaccount --subscription SUB_ID --resource-group RG
    """
    logger.info("Discovering models for account %r", account_name)
    asyncio.run(
        _discover_async(account_name, subscription, resource_group, include_pricing, output_format)
    )


async def _discover_async(
    account_name: str,
    subscription: str | None,
    resource_group: str | None,
    include_pricing: bool,
    output_format: str,
) -> None:
    """Async implementation backing the discover command."""
    from sage_evaluator.discovery.azure_models import AzureModelDiscovery
    from sage_evaluator.discovery.pricing import PricingLookup
    from sage_evaluator.exceptions import DiscoveryError
    from sage_evaluator.models import DeployedModel, ModelPricing
    from sage_evaluator.reporting.terminal import print_discovered_models

    discovery = AzureModelDiscovery(
        account_name,
        subscription_id=subscription,
        resource_group=resource_group,
    )

    try:
        models: list[DeployedModel] = await discovery.list_models()
    except DiscoveryError as exc:
        raise click.ClickException(str(exc)) from exc

    pricing_map: dict[str, ModelPricing] | None = None
    if include_pricing:
        lookup = PricingLookup()
        pricing_map = {m.litellm_id: lookup.get_pricing(m.litellm_id) for m in models}

    if output_format == "json":
        output: list[dict] = []
        for model in models:
            entry: dict = model.model_dump()
            if pricing_map is not None:
                p = pricing_map.get(model.litellm_id)
                entry["pricing"] = p.model_dump() if p else None
            output.append(entry)
        click.echo(json.dumps(output, indent=2))
    else:
        print_discovered_models(models, pricing=pricing_map)


# ── benchmark command ────────────────────────────────────────────────────────


@cli.command("benchmark")
@click.argument("config", type=click.Path(exists=True))
@click.option(
    "--account-name",
    envvar="AZURE_AI_ACCOUNT_NAME",
    default=None,
    help="Azure Cognitive Services account name for model discovery.",
)
@click.option(
    "--subscription",
    envvar="AZURE_SUBSCRIPTION_ID",
    default=None,
    help="Azure subscription ID (used with --account-name).",
)
@click.option(
    "--resource-group",
    envvar="AZURE_RESOURCE_GROUP",
    default=None,
    help="Azure resource group (used with --account-name).",
)
@click.option(
    "--models",
    "-m",
    multiple=True,
    help="Model identifiers to benchmark (repeatable).",
)
@click.option("--intent", required=True, help="User intent to benchmark against.")
@click.option(
    "--rubric",
    default="default",
    show_default=True,
    help="Rubric name (default, code_generation, qa) or path to YAML rubric.",
)
@click.option("--runs", default=1, show_default=True, help="Number of runs per model.")
@click.option(
    "--no-judge",
    is_flag=True,
    default=False,
    help="Skip LLM-as-judge evaluation (metrics only).",
)
@click.option("--output", "output_path", default=None, help="Save report to JSON file.")
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["text", "json"], case_sensitive=False),
    default="text",
    show_default=True,
    help="Output format.",
)
@click.pass_context
def benchmark_command(
    ctx: click.Context,
    config: str,
    account_name: str | None,
    subscription: str | None,
    resource_group: str | None,
    models: tuple[str, ...],
    intent: str,
    rubric: str,
    runs: int,
    no_judge: bool,
    output_path: str | None,
    output_format: str,
) -> None:
    """Benchmark an agent configuration across one or more models."""
    from sage_evaluator.benchmark.engine import BenchmarkEngine

    evaluator_model = _get_default_model(ctx)
    main_config = _get_main_config(ctx)

    if not models:
        click.echo("Error: at least one --models/-m is required.", err=True)
        sys.exit(1)

    logger.info(
        "Starting benchmark: config=%s, models=%s, runs=%d, intent=%r",
        config,
        list(models),
        runs,
        intent,
    )

    async def _run():
        engine = BenchmarkEngine(
            evaluator_model=evaluator_model,
            rubric=rubric,
            runs=runs,
            use_judge=not no_judge,
            main_config=main_config,
        )
        report = await engine.run(
            config_path=config,
            models=list(models),
            intent=intent,
        )

        if output_path:
            from sage_evaluator.reporting.json_export import export_benchmark_report

            export_benchmark_report(report, output_path)
            click.echo(f"Report saved to {output_path}")

        if output_format == "json":
            click.echo(json.dumps(report.model_dump(), indent=2, default=str))
        else:
            from sage_evaluator.reporting.terminal import print_benchmark_report

            print_benchmark_report(report)

    asyncio.run(_run())


# ── suggest command ──────────────────────────────────────────────────────────


@cli.command("suggest")
@click.argument("config", type=click.Path(exists=True))
@click.option(
    "--generate-tools",
    is_flag=True,
    default=False,
    help="Generate @tool function code from extraction suggestions.",
)
@click.option(
    "--generate-guardrails",
    is_flag=True,
    default=False,
    help="Generate guardrail @tool functions.",
)
@click.option("--output", "output_path", default=None, help="Save report to JSON file.")
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["text", "json"], case_sensitive=False),
    default="text",
    show_default=True,
    help="Output format.",
)
@click.pass_context
def suggest_command(
    ctx: click.Context,
    config: str,
    generate_tools: bool,
    generate_guardrails: bool,
    output_path: str | None,
    output_format: str,
) -> None:
    """Analyze an agent configuration and suggest optimizations.

    CONFIG is the path to an AGENTS.md file or a directory containing one.

    \b
    Examples:
        evaluate suggest ./my-agent/AGENTS.md
        evaluate suggest ./my-agent --generate-tools --generate-guardrails
        evaluate suggest ./my-agent --format json --output report.json
    """
    from sage_evaluator.exceptions import SuggestionError
    from sage_evaluator.suggestion.analyzer import PromptAnalyzer

    analyzer_model = _get_default_model(ctx)
    main_config = _get_main_config(ctx)

    logger.info("Starting suggestion analysis: config=%s, model=%s", config, analyzer_model)

    async def _run():
        analyzer = PromptAnalyzer(model=analyzer_model)
        click.echo(f"Analyzing config: {config}")
        click.echo(f"Using model: {analyzer_model}")

        try:
            report = await analyzer.analyze(config, central=main_config)
        except SuggestionError as exc:
            raise click.ClickException(str(exc)) from exc

        click.echo(f"Found {len(report.suggestions)} suggestion(s).")

        all_generated_tools = list(report.generated_tools)

        if generate_tools and report.suggestions:
            click.echo("Generating @tool functions from tool_extraction suggestions...")
            try:
                from sage_evaluator.suggestion.tool_generator import ToolGenerator

                gen = ToolGenerator(model=analyzer_model)
                tools = await gen.generate_tools(report.suggestions)
                all_generated_tools.extend(tools)
                click.echo(f"Generated {len(tools)} tool(s).")
            except SuggestionError as exc:
                click.echo(f"Warning: tool generation failed: {exc}", err=True)

        if generate_guardrails and report.suggestions:
            click.echo("Generating guardrail functions from guardrail suggestions...")
            try:
                from sage_evaluator.suggestion.guardrail_generator import GuardrailGenerator

                gen = GuardrailGenerator(model=analyzer_model)
                guardrails = await gen.generate_guardrails(report.suggestions)
                all_generated_tools.extend(guardrails)
                click.echo(f"Generated {len(guardrails)} guardrail(s).")
            except SuggestionError as exc:
                click.echo(f"Warning: guardrail generation failed: {exc}", err=True)

        # Rebuild the report (Pydantic model) with generated tools attached.
        report = report.model_copy(update={"generated_tools": all_generated_tools})

        if output_path:
            output_file = Path(output_path)
            output_file.write_text(report.model_dump_json(indent=2), encoding="utf-8")
            click.echo(f"Report saved to {output_path}")

        if output_format == "json":
            click.echo(report.model_dump_json(indent=2))
        else:
            from sage_evaluator.reporting.terminal import print_suggestion_report

            print_suggestion_report(report)

    asyncio.run(_run())


# ── compare command ──────────────────────────────────────────────────────────


@cli.command("compare")
@click.argument("config_a", type=click.Path(exists=True))
@click.argument("config_b", type=click.Path(exists=True))
@click.option(
    "--account-name",
    envvar="AZURE_AI_ACCOUNT_NAME",
    default=None,
    help="Azure Cognitive Services account name for model discovery.",
)
@click.option(
    "--subscription",
    envvar="AZURE_SUBSCRIPTION_ID",
    default=None,
    help="Azure subscription ID (used with --account-name).",
)
@click.option(
    "--resource-group",
    envvar="AZURE_RESOURCE_GROUP",
    default=None,
    help="Azure resource group (used with --account-name).",
)
@click.option(
    "--models",
    "-m",
    multiple=True,
    help="Model identifiers to benchmark (repeatable).",
)
@click.option("--intent", required=True, help="User intent to benchmark against.")
@click.option(
    "--rubric",
    default="default",
    show_default=True,
    help="Rubric name or path to YAML rubric.",
)
@click.option("--runs", default=1, show_default=True, help="Number of runs per model.")
@click.option(
    "--no-judge",
    is_flag=True,
    default=False,
    help="Skip LLM-as-judge evaluation.",
)
@click.option("--output", "output_path", default=None, help="Save report to JSON file.")
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["text", "json"], case_sensitive=False),
    default="text",
    show_default=True,
    help="Output format.",
)
@click.pass_context
def compare_command(
    ctx: click.Context,
    config_a: str,
    config_b: str,
    account_name: str | None,
    subscription: str | None,
    resource_group: str | None,
    models: tuple[str, ...],
    intent: str,
    rubric: str,
    runs: int,
    no_judge: bool,
    output_path: str | None,
    output_format: str,
) -> None:
    """Compare two agent configurations side-by-side against the same intent."""
    from sage_evaluator.benchmark.engine import BenchmarkEngine
    from sage_evaluator.models import CompareResult

    evaluator_model = _get_default_model(ctx)
    main_config = _get_main_config(ctx)

    if not models:
        click.echo("Error: at least one --models/-m is required.", err=True)
        sys.exit(1)

    logger.info("Comparing configs: A=%s, B=%s, models=%s", config_a, config_b, list(models))

    async def _run():
        engine = BenchmarkEngine(
            evaluator_model=evaluator_model,
            rubric=rubric,
            runs=runs,
            use_judge=not no_judge,
            main_config=main_config,
        )

        report_a = await engine.run(config_path=config_a, models=list(models), intent=intent)
        report_b = await engine.run(config_path=config_b, models=list(models), intent=intent)

        compare = CompareResult(
            config_a_path=config_a,
            config_b_path=config_b,
            report_a=report_a,
            report_b=report_b,
        )

        if output_path:
            from sage_evaluator.reporting.json_export import export_compare_result

            export_compare_result(compare, output_path)
            click.echo(f"Report saved to {output_path}")

        if output_format == "json":
            click.echo(json.dumps(compare.model_dump(), indent=2, default=str))
        else:
            from sage_evaluator.reporting.terminal import print_compare_report

            print_compare_report(compare)

    asyncio.run(_run())
