"""CLI commands for Scout checks."""

from __future__ import annotations

import json
import os
import sys
import time
from pathlib import Path
from typing import Any, Dict, List

import click
import yaml

from g_gremlin.safety import SafetyContext
from g_gremlin import cli_telemetry
from g_gremlin.provenance import build_provenance, default_provenance_dir, write_sidecar
from g_gremlin.webhook_out.emitter import WebhookEmitter
from g_gremlin.webhook_out import envelope as webhook_envelope

from .actions import (
    DEFAULT_FLAG_FIELD,
    create_tasks,
    create_cluster_tasks,
    export_csv,
    export_merge_worksheet,
    flag_records,
    flag_cluster_records,
)
from .engine import ScoutEngine
from .loader import DEFAULT_SCOUTS_DIR, USER_SCOUTS_DIR, load_scan_config
from .models import ScoutFinding, ScoutScanResult
from .renderers import render_finding_table, render_scan_summary
from .seen_store import SeenStore

EXIT_SUCCESS = 0
EXIT_ISSUES = 1
EXIT_USAGE = 2
EXIT_REMOTE_ERROR = 3


def _get_user_credentials() -> set[str]:
    try:
        from g_gremlin.credentials.provider import CredentialProvider

        provider = CredentialProvider()
        return set(provider.list_configured_integrations())
    except Exception:
        return set()


def _parse_param_overrides(param: tuple[str, ...]) -> Dict[str, Any]:
    params: Dict[str, Any] = {}
    for p in param:
        if "=" not in p:
            raise click.ClickException(f"Invalid parameter format: {p} (expected key=value)")
        key, value = p.split("=", 1)
        params[key] = value
    return params


def _emit_webhook(result: Any, webhook_out: str | None, webhook_verbose: bool) -> None:
    if not webhook_out:
        return
    emitter = WebhookEmitter()
    try:
        delivery = emitter.emit(result, webhook_out)
        masked = webhook_envelope.mask_url(webhook_out) if not webhook_out.startswith("@") else webhook_out
        if delivery.success:
            click.echo(f"Webhook delivered: {delivery.delivery_id} -> {masked}", err=True)
        else:
            click.echo(f"[WARN] Webhook failed: {delivery.error} -> {masked}", err=True)
    except Exception as exc:
        click.echo(f"Webhook error: {webhook_envelope.redact_sensitive(str(exc))}", err=True)


def _write_output(path: Path, payload: Dict[str, Any], command: str, args: Dict[str, Any]) -> Dict[str, str]:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    artifacts: Dict[str, str] = {"output_path": str(path)}
    try:
        prov = build_provenance(command=command, args=args, inputs=[])
        sidecar_dir = default_provenance_dir()
        sidecar = write_sidecar(path, prov, provenance_dir=sidecar_dir)
        artifacts["provenance_path"] = str(sidecar)
    except Exception:
        click.echo("Warning: failed to write provenance sidecar.", err=True)
    return artifacts


@click.group()
def scout() -> None:
    """
    CRM hygiene scouts (diagnostic checks).
    """


@scout.command("list")
@click.option("--json-out", "json_out", is_flag=True, help="Output JSON.")
def scout_list(json_out: bool) -> None:
    """List available scout checks and credential status."""
    user_creds = _get_user_credentials()
    scouts: Dict[str, Dict[str, Any]] = {}

    for scout_dir in (DEFAULT_SCOUTS_DIR, USER_SCOUTS_DIR):
        if not scout_dir.exists():
            continue
        for yaml_file in scout_dir.glob("*.yaml"):
            if yaml_file.name.startswith("_"):
                continue
            try:
                data = yaml.safe_load(yaml_file.read_text(encoding="utf-8"))
            except Exception:
                continue
            if not data or not data.get("name"):
                continue
            scouts[data["name"]] = data

    if not scouts:
        click.echo("No scouts found.")
        return

    if json_out:
        payload = []
        for scout_data in scouts.values():
            required = scout_data.get("requires_credentials", [])
            has_creds = not required or all(c in user_creds for c in required)
            payload.append(
                {
                    "name": scout_data.get("name"),
                    "display_name": scout_data.get("display_name"),
                    "description": scout_data.get("description"),
                    "category": scout_data.get("category"),
                    "severity_default": scout_data.get("severity_default"),
                    "enabled": scout_data.get("enabled", True),
                    "credentials_configured": has_creds,
                }
            )
        click.echo(json.dumps(payload, indent=2))
        return

    click.echo("Available scouts:")
    for scout_data in scouts.values():
        required = scout_data.get("requires_credentials", [])
        has_creds = not required or all(c in user_creds for c in required)
        status = "[Y]" if has_creds else "[N]"
        name = scout_data.get("name")
        display_name = scout_data.get("display_name") or name
        category = scout_data.get("category", "general")
        click.echo(f"  {display_name} ({name}) {status} [{category}]")
        desc = scout_data.get("description", "")
        if desc:
            click.echo(f"     {desc}")


@scout.command("run")
@click.argument("name")
@click.option("--param", "--params", "param", multiple=True, help="Parameter override key=value.")
@click.option("--org-alias", default=None, help="Salesforce org alias.")
@click.option(
    "--crm",
    "crm",
    type=click.Choice(["salesforce", "hubspot", "dynamics"], case_sensitive=False),
    default=None,
    help="CRM provider to use.",
)
@click.option("--hubspot-profile", default=None, help="HubSpot profile name.")
@click.option("--dynamics-profile", default=None, help="Dynamics profile name.")
@click.option("--dynamics-profile", default=None, help="Dynamics profile name.")
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["table", "json"], case_sensitive=False),
    default="table",
    show_default=True,
)
@click.option("--output", "output_path", type=click.Path(dir_okay=False, path_type=Path))
@click.option("--webhook-out", "webhook_out", help="Webhook URL or @profile.")
@click.option("--only-new", is_flag=True, help="Only show findings not seen recently.")
@click.option("--webhook-verbose", is_flag=True, help="Include field values in webhook payload.")
@click.option(
    "--artifact-base-url",
    "artifact_base_url",
    default=None,
    help="Base URL for artifact links in webhook payloads.",
)
def scout_run(
    name: str,
    param: tuple[str, ...],
    org_alias: str | None,
    crm: str | None,
    hubspot_profile: str | None,
    dynamics_profile: str | None,
    output_format: str,
    output_path: Path | None,
    webhook_out: str | None,
    only_new: bool,
    webhook_verbose: bool,
    artifact_base_url: str | None,
) -> None:
    """Run a single scout check by name and display findings."""
    start_time = time.perf_counter()
    safety_ctx = SafetyContext(command="g-gremlin scout run", remote_mutation=False)
    params = _parse_param_overrides(param)
    user_creds = _get_user_credentials()
    engine = ScoutEngine(user_credentials=user_creds)

    scan_defaults = load_scan_config()
    top_n = scan_defaults.top_n
    resolved_crm = (crm or os.getenv("G_GREMLIN_CRM") or scan_defaults.crm or "salesforce").lower()
    resolved_profile = hubspot_profile or scan_defaults.hubspot_profile
    resolved_dynamics_profile = dynamics_profile or scan_defaults.dynamics_profile

    finding = engine.run_scout(
        name,
        params=params,
        top_n=top_n,
        org_alias=org_alias,
        crm=resolved_crm,
        hubspot_profile=resolved_profile,
        dynamics_profile=resolved_dynamics_profile,
    )
    original_finding = finding

    store = SeenStore()
    if only_new:
        filtered = store.filter_new([finding])
        if filtered:
            finding = filtered[0]

    # Always mark seen after evaluation
    try:
        store.mark_seen([original_finding])
    except Exception:
        pass

    artifacts: Dict[str, str] = {}
    if output_path:
        artifacts = _write_output(
            output_path,
            finding.to_dict(include_fields=True, include_reason=True),
            command=f"g-gremlin scout run {name}",
            args={
                "name": name,
                "params": params,
                "org_alias": org_alias,
                "format": output_format,
                "crm": resolved_crm,
                "hubspot_profile": resolved_profile,
                "dynamics_profile": resolved_dynamics_profile,
            },
        )
        click.echo(f"Wrote finding JSON to {output_path}")

    if output_format == "json":
        click.echo(json.dumps(finding.to_dict(include_fields=True, include_reason=True), indent=2))
    else:
        click.echo(render_finding_table(finding, include_fields=True))

    # Webhook (PII-safe by default)
    if webhook_out:
        agentic = finding.to_agentic_result(
            include_fields=webhook_verbose,
            include_reason=webhook_verbose,
        )
        agentic.result.setdefault("meta", {})
        agentic.result["meta"].update(
            {
                "crm": resolved_crm,
                "org_alias": org_alias,
                "only_new": only_new,
                "top_n": top_n,
                "artifact_base_url": artifact_base_url or os.getenv("G_GREMLIN_ARTIFACT_BASE_URL"),
                "output_dir": str(output_path.parent) if output_path else None,
            }
        )
        if artifacts:
            agentic.result["artifacts"] = artifacts
        _emit_webhook(agentic, webhook_out, webhook_verbose)

    duration_ms = int((time.perf_counter() - start_time) * 1000)
    cli_telemetry.emit_cli_event(
        command=f"scout.run.{name}",
        status="success" if finding.status in ("ok", "findings") else "error",
        duration_ms=duration_ms,
        correlation_id=safety_ctx.correlation_id,
        exit_code=EXIT_SUCCESS,
        attrs={"findings_count": finding.findings_count},
    )


@scout.command("scan")
@click.option("--config", "config_path", type=click.Path(dir_okay=False, path_type=Path))
@click.option("--org-alias", default=None, help="Salesforce org alias.")
@click.option(
    "--crm",
    "crm",
    type=click.Choice(["salesforce", "hubspot", "dynamics"], case_sensitive=False),
    default=None,
    help="CRM provider to use.",
)
@click.option("--hubspot-profile", default=None, help="HubSpot profile name.")
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["table", "json"], case_sensitive=False),
    default="table",
    show_default=True,
)
@click.option("--output", "output_path", type=click.Path(dir_okay=False, path_type=Path))
@click.option("--webhook-out", "webhook_out", help="Webhook URL or @profile.")
@click.option("--only-new", is_flag=True, help="Only show findings not seen recently.")
@click.option("--webhook-verbose", is_flag=True, help="Include field values in webhook payload.")
@click.option(
    "--artifact-base-url",
    "artifact_base_url",
    default=None,
    help="Base URL for artifact links in webhook payloads.",
)
def scout_scan(
    config_path: Path | None,
    org_alias: str | None,
    crm: str | None,
    hubspot_profile: str | None,
    dynamics_profile: str | None,
    output_format: str,
    output_path: Path | None,
    webhook_out: str | None,
    only_new: bool,
    webhook_verbose: bool,
    artifact_base_url: str | None,
) -> None:
    """Run all enabled scouts from a scan config and aggregate findings."""
    start_time = time.perf_counter()
    safety_ctx = SafetyContext(command="g-gremlin scout scan", remote_mutation=False)
    user_creds = _get_user_credentials()
    engine = ScoutEngine(user_credentials=user_creds)

    scan_cfg = load_scan_config(config_path)
    resolved_crm = (crm or os.getenv("G_GREMLIN_CRM") or scan_cfg.crm or "salesforce").lower()
    resolved_profile = hubspot_profile or scan_cfg.hubspot_profile
    resolved_dynamics_profile = dynamics_profile or scan_cfg.dynamics_profile
    scout_names = scan_cfg.scouts_enabled or [s.name for s in engine.list_scouts()]

    scan_result = engine.run_scan(
        scout_names,
        top_n=scan_cfg.top_n,
        per_scout_params=scan_cfg.per_scout_params,
        org_alias=org_alias or scan_cfg.org_alias,
        crm=resolved_crm,
        hubspot_profile=resolved_profile,
        dynamics_profile=resolved_dynamics_profile,
    )

    original_findings = scan_result.findings
    store = SeenStore()
    if only_new:
        scan_result.findings = store.filter_new(scan_result.findings)

    try:
        store.mark_seen(original_findings)
    except Exception:
        pass

    scan_result.ended_at = scan_result.ended_at or scan_result.started_at

    artifacts: Dict[str, str] = {}
    if output_path:
        artifacts = _write_output(
            output_path,
            scan_result.to_dict(include_fields=True, include_reason=True),
            command="g-gremlin scout scan",
            args={
                "config": str(config_path) if config_path else None,
                "org_alias": org_alias,
                "format": output_format,
                "crm": resolved_crm,
                "hubspot_profile": resolved_profile,
                "dynamics_profile": resolved_dynamics_profile,
            },
        )
        click.echo(f"Wrote scan JSON to {output_path}")

    if output_format == "json":
        click.echo(json.dumps(scan_result.to_dict(include_fields=True, include_reason=True), indent=2))
    else:
        click.echo(render_scan_summary(scan_result))

    if webhook_out:
        agentic = scan_result.to_agentic_result(
            include_fields=webhook_verbose,
            include_reason=webhook_verbose,
        )
        agentic.result.setdefault("meta", {})
        agentic.result["meta"].update(
            {
                "crm": resolved_crm,
                "org_alias": org_alias or scan_cfg.org_alias,
                "only_new": only_new,
                "top_n": scan_cfg.top_n,
                "artifact_base_url": artifact_base_url or os.getenv("G_GREMLIN_ARTIFACT_BASE_URL"),
                "output_dir": str(output_path.parent) if output_path else None,
            }
        )
        if artifacts:
            agentic.result["artifacts"] = artifacts
        _emit_webhook(agentic, webhook_out, webhook_verbose)

    duration_ms = int((time.perf_counter() - start_time) * 1000)
    cli_telemetry.emit_cli_event(
        command="scout.scan",
        status="success" if not scan_result.errors else "partial",
        duration_ms=duration_ms,
        correlation_id=safety_ctx.correlation_id,
        exit_code=EXIT_SUCCESS,
        attrs={"findings_count": sum(f.findings_count for f in scan_result.findings)},
    )


@scout.command("apply")
@click.argument("finding_path", type=click.Path(dir_okay=False, path_type=Path))
@click.option(
    "--action",
    "action_type",
    type=click.Choice(
        [
            "export_csv",
            "flag_record",
            "create_task",
            "create_cluster_tasks",
            "flag_cluster_records",
            "export_merge_worksheet",
        ],
        case_sensitive=False,
    ),
    required=True,
)
@click.option("--output", "output_path", type=click.Path(dir_okay=False, path_type=Path))
@click.option("--field", "field_name", default=DEFAULT_FLAG_FIELD, help="Field to set for flag_record.")
@click.option("--subject", "subject_template", default="Follow up: {reason}")
@click.option("--assignee-id", "assignee_id", default=None, help="OwnerId/assignee for cluster tasks.")
@click.option(
    "--confidence-threshold",
    "confidence_threshold",
    default="medium",
    type=click.Choice(["low", "medium", "high"], case_sensitive=False),
    help="Minimum confidence to include in merge worksheet.",
)
@click.option("--org-alias", default=None, help="Salesforce org alias.")
@click.option(
    "--crm",
    "crm",
    type=click.Choice(["salesforce", "hubspot", "dynamics"], case_sensitive=False),
    default=None,
    help="CRM provider to use.",
)
@click.option("--hubspot-profile", default=None, help="HubSpot profile name.")
@click.option("--dynamics-profile", default=None, help="Dynamics profile name.")
@click.option("--apply", "do_apply", is_flag=True, help="Execute write actions.")
def scout_apply(
    finding_path: Path,
    action_type: str,
    output_path: Path | None,
    field_name: str,
    subject_template: str,
    assignee_id: str | None,
    confidence_threshold: str,
    org_alias: str | None,
    crm: str | None,
    hubspot_profile: str | None,
    dynamics_profile: str | None,
    do_apply: bool,
) -> None:
    """Act on a scout finding (export, flag, create task, merge worksheet)."""
    if not finding_path.exists():
        click.echo(f"Finding file not found: {finding_path}", err=True)
        sys.exit(EXIT_USAGE)

    payload = json.loads(finding_path.read_text(encoding="utf-8"))
    finding = ScoutFinding.from_dict(payload)

    if action_type == "export_csv":
        if not output_path:
            click.echo("--output is required for export_csv", err=True)
            sys.exit(EXIT_USAGE)
        export_csv(finding, output_path)
        click.echo(f"Exported CSV to {output_path}")
        return
    if action_type == "export_merge_worksheet":
        if not output_path:
            click.echo("--output is required for export_merge_worksheet", err=True)
            sys.exit(EXIT_USAGE)
        export_merge_worksheet(finding, output_path, confidence_threshold=confidence_threshold)
        click.echo(f"Exported merge worksheet to {output_path}")
        return

    safety_ctx = SafetyContext(command="g-gremlin scout apply", remote_mutation=True)
    if not do_apply:
        click.echo("Dry-run preview (use --apply to execute):")

    resolved_crm = (crm or os.getenv("G_GREMLIN_CRM") or "salesforce").lower()

    if action_type == "flag_record":
        if do_apply:
            safety_ctx.enforce_apply(do_apply)
        result = flag_records(
            finding,
            field_name,
            org_alias=org_alias,
            apply=do_apply,
            crm=resolved_crm,
            hubspot_profile=hubspot_profile,
            dynamics_profile=dynamics_profile,
        )
        click.echo(
            f"Flagged {len(result['updated'])} record(s); failures: {len(result['failed'])}"
        )
    elif action_type == "flag_cluster_records":
        if do_apply:
            safety_ctx.enforce_apply(do_apply)
        result = flag_cluster_records(
            finding,
            field_name,
            org_alias=org_alias,
            apply=do_apply,
            crm=resolved_crm,
            hubspot_profile=hubspot_profile,
            dynamics_profile=dynamics_profile,
        )
        click.echo(
            f"Flagged {len(result['updated'])} record(s); failures: {len(result['failed'])}"
        )
    elif action_type == "create_task":
        if do_apply:
            safety_ctx.enforce_apply(do_apply)
        result = create_tasks(
            finding,
            subject_template=subject_template,
            org_alias=org_alias,
            apply=do_apply,
            crm=resolved_crm,
            hubspot_profile=hubspot_profile,
            dynamics_profile=dynamics_profile,
        )
        click.echo(
            f"Tasks created for {len(result['created'])} record(s); failures: {len(result['failed'])}"
        )
    elif action_type == "create_cluster_tasks":
        if do_apply:
            safety_ctx.enforce_apply(do_apply)
        result = create_cluster_tasks(
            finding,
            subject_template=subject_template,
            assignee_id=assignee_id,
            org_alias=org_alias,
            apply=do_apply,
            crm=resolved_crm,
            hubspot_profile=hubspot_profile,
            dynamics_profile=dynamics_profile,
        )
        click.echo(
            f"Cluster tasks created: {len(result['created'])}; failures: {len(result['failed'])}"
        )
    else:
        click.echo(f"Unknown action: {action_type}", err=True)
        sys.exit(EXIT_USAGE)


@scout.command("config")
@click.option("--show", is_flag=True, help="Show effective scan config.")
def scout_config(show: bool) -> None:
    """Show or manage the scout scan configuration."""
    if not show:
        click.echo("Use --show to display scan configuration.")
        return
    cfg = load_scan_config()
    click.echo(json.dumps(cfg.to_dict(), indent=2))


__all__ = ["scout"]
