"""Stable JSON CLI contracts for IDE-agent consumption."""

from __future__ import annotations

from pathlib import Path
from typing import Any

from g_gremlin.autopilot.ai_provider import AIProviderStore
from g_gremlin.autopilot.audit_log import AuditLog
from g_gremlin.autopilot.concierge_executor import ConciergeExecutor
from g_gremlin.autopilot.concierge_planner import ConciergePlanner
from g_gremlin.autopilot.discovery import DiscoveryRunner, DiscoveryStore
from g_gremlin.autopilot.field_provenance import FieldProvenanceStore
from g_gremlin.autopilot.identity_graph import IdentityGraph
from g_gremlin.autopilot.modules import ModuleStore
from g_gremlin.autopilot.scout_config_store import ScoutConfigStore
from g_gremlin.scout.engine import ScoutEngine
from g_gremlin.scout.loader import ScoutLoader


def cli_discovery_run(
    state_dir: Path,
    *,
    crm: str = "salesforce",
    org_alias: str | None = None,
    user_credentials: set[str] | None = None,
) -> dict[str, Any]:
    """Run discovery and return the report as a JSON-serializable dict."""
    creds = user_credentials or set()
    store = DiscoveryStore(state_dir)
    loader = ScoutLoader(user_credentials=creds)
    engine = ScoutEngine(loader, user_credentials=creds)
    overrides = ScoutConfigStore(state_dir).load_all()
    ai_store = AIProviderStore(state_dir)
    ai_config = ai_store.load_config()
    runner = DiscoveryRunner(
        store,
        engine,
        crm=crm,
        org_alias=org_alias,
        overrides=overrides,
        ai_config=ai_config if ai_config and ai_config.enabled else None,
    )
    report = runner.run()
    return {"status": "completed", "run_id": report.run_id, "report": report.to_dict()}


def cli_discovery_latest(state_dir: Path) -> dict[str, Any]:
    """Return the latest discovery report."""
    store = DiscoveryStore(state_dir)
    run_id = store.latest_run_id()
    if not run_id:
        return {"status": "empty", "run": None, "report": None}
    run = store.load_run(run_id)
    report = store.load_report(run_id)
    return {
        "status": "ok",
        "run": run.to_dict() if run else None,
        "report": report.to_dict() if report else None,
    }


def cli_concierge_preview(
    state_dir: Path,
    *,
    run_id: str | None = None,
    user_credentials: set[str] | None = None,
) -> dict[str, Any]:
    """Preview concierge plan for the given (or latest) discovery run."""
    creds = user_credentials or set()
    store = DiscoveryStore(state_dir)
    target_run_id = run_id or store.latest_run_id()
    if not target_run_id:
        return {"plan": None, "report_run_id": None}
    report = store.load_report(target_run_id)
    if not report:
        return {"plan": None, "report_run_id": None}

    loader = ScoutLoader(user_credentials=creds)
    engine = ScoutEngine(loader, user_credentials=creds)
    planner = ConciergePlanner(
        report,
        ScoutConfigStore(state_dir),
        ModuleStore(state_dir),
        loader,
        engine,
    )
    plan = planner.plan()
    return {"plan": plan.model_dump(exclude_none=False), "report_run_id": report.run_id}


def cli_concierge_apply(
    state_dir: Path,
    *,
    run_id: str,
    action_ids: list[str] | None = None,
    dry_run: bool = True,
    actor: str = "cli",
    user_credentials: set[str] | None = None,
) -> dict[str, Any]:
    """Apply concierge plan and return the receipt."""
    creds = user_credentials or set()
    store = DiscoveryStore(state_dir)
    report = store.load_report(run_id)
    if not report:
        return {"error": f"Discovery run not found: {run_id}"}

    loader = ScoutLoader(user_credentials=creds)
    engine = ScoutEngine(loader, user_credentials=creds)
    planner = ConciergePlanner(
        report,
        ScoutConfigStore(state_dir),
        ModuleStore(state_dir),
        loader,
        engine,
    )
    plan = planner.plan()
    if action_ids is not None:
        selected = [action for action in plan.actions if action.action_id in set(action_ids)]
        plan = plan.model_copy(
            update={
                "actions": selected,
                "stats": ConciergePlanner.compute_stats(selected),
            }
        )

    audit = AuditLog(Path(state_dir) / "db" / "platform.sqlite")
    executor = ConciergeExecutor(
        ScoutConfigStore(state_dir),
        ModuleStore(state_dir),
        state_dir,
        audit_log=audit,
    )
    receipt = executor.execute(plan, dry_run=dry_run, actor=actor)
    return {"receipt": receipt.model_dump(exclude_none=False), "report_run_id": report.run_id}


def cli_substrate_stats(state_dir: Path) -> dict[str, Any]:
    """Return identity/provenance aggregate stats for reporting."""
    db_path = Path(state_dir) / "db" / "platform.sqlite"
    identity = IdentityGraph(db_path)
    provenance = FieldProvenanceStore(db_path)
    return {
        "identity": identity.stats(),
        "provenance": {
            **provenance.stats(),
            "by_source_system": provenance.source_system_counts(),
        },
    }


def cli_substrate_identity_lookup(
    state_dir: Path,
    *,
    source_system: str | None = None,
    source_record_id: str | None = None,
    source_org_id: str | None = None,
    email: str | None = None,
    domain: str | None = None,
) -> dict[str, Any]:
    """Resolve a canonical entity and linked records using stable lookup keys."""
    normalized_system = str(source_system or "").strip().lower()
    normalized_record_id = str(source_record_id or "").strip()
    normalized_org_id = str(source_org_id or "").strip() or None
    normalized_email = str(email or "").strip()
    normalized_domain = str(domain or "").strip()

    has_source_lookup = bool(normalized_system or normalized_record_id)
    if has_source_lookup and not (normalized_system and normalized_record_id):
        return {"error": "source_system and source_record_id are required together"}
    if not has_source_lookup and not normalized_email and not normalized_domain:
        return {"error": "provide source lookup, email, or domain"}

    db_path = Path(state_dir) / "db" / "platform.sqlite"
    identity = IdentityGraph(db_path)
    entity: dict[str, Any] | None = None
    lookup_kind = "unknown"
    if normalized_system and normalized_record_id:
        entity = identity.find_entity_by_record(
            normalized_system,
            normalized_record_id,
            normalized_org_id,
        )
        lookup_kind = "source_record"
    elif normalized_email:
        entity = identity.find_entity_by_email(normalized_email)
        lookup_kind = "email"
    else:
        entity = identity.find_entity_by_domain(normalized_domain)
        lookup_kind = "domain"

    if not entity:
        return {"found": False, "lookup_kind": lookup_kind, "entity": None, "linked_records": []}

    entity_id = str(entity.get("entity_id") or "")
    linked_records = identity.get_linked_records(entity_id)
    return {
        "found": True,
        "lookup_kind": lookup_kind,
        "entity": entity,
        "linked_records": linked_records,
    }


def cli_substrate_provenance_entity(
    state_dir: Path,
    *,
    entity_id: str,
    field_name: str | None = None,
    include_drift: bool = True,
    limit: int = 200,
) -> dict[str, Any]:
    """Return observations + authoritative values for a canonical entity."""
    normalized_entity_id = str(entity_id or "").strip()
    normalized_field_name = str(field_name or "").strip().lower() or None
    if not normalized_entity_id:
        return {"error": "entity_id is required"}

    db_path = Path(state_dir) / "db" / "platform.sqlite"
    provenance = FieldProvenanceStore(db_path)
    observations = provenance.list_entity_observations(
        normalized_entity_id,
        field_name=normalized_field_name,
        limit=limit,
    )

    if normalized_field_name:
        authoritative = provenance.get_authoritative_value(normalized_entity_id, normalized_field_name)
        drift = provenance.detect_drift(normalized_entity_id, normalized_field_name) if include_drift else []
        return {
            "entity_id": normalized_entity_id,
            "field_name": normalized_field_name,
            "observations": observations,
            "authoritative": authoritative,
            "drift": drift,
        }

    field_names: list[str] = []
    seen_fields: set[str] = set()
    for observation in observations:
        name = str(observation.get("field_name") or "").strip().lower()
        if not name or name in seen_fields:
            continue
        seen_fields.add(name)
        field_names.append(name)

    authoritative_by_field: dict[str, dict[str, Any]] = {}
    drift_by_field: dict[str, list[dict[str, Any]]] = {}
    for name in field_names:
        authoritative = provenance.get_authoritative_value(normalized_entity_id, name)
        if authoritative is not None:
            authoritative_by_field[name] = authoritative
        if include_drift:
            drift_rows = provenance.detect_drift(normalized_entity_id, name)
            if drift_rows:
                drift_by_field[name] = drift_rows

    return {
        "entity_id": normalized_entity_id,
        "observations": observations,
        "authoritative_by_field": authoritative_by_field,
        "drift_by_field": drift_by_field if include_drift else {},
    }


def cli_substrate_stale_fields(
    state_dir: Path,
    *,
    older_than_days: int = 90,
    limit: int = 200,
) -> dict[str, Any]:
    """Return stale provenance rows for cleanup/reporting."""
    db_path = Path(state_dir) / "db" / "platform.sqlite"
    provenance = FieldProvenanceStore(db_path)
    rows = provenance.stale_fields(older_than_days=older_than_days)
    max_limit = max(1, min(int(limit), 1000))
    results = rows[:max_limit]
    return {
        "older_than_days": max(int(older_than_days), 0),
        "count": len(results),
        "truncated": len(rows) > max_limit,
        "results": results,
    }


__all__ = [
    "cli_discovery_run",
    "cli_discovery_latest",
    "cli_concierge_preview",
    "cli_concierge_apply",
    "cli_substrate_stats",
    "cli_substrate_identity_lookup",
    "cli_substrate_provenance_entity",
    "cli_substrate_stale_fields",
]
