"""HubSpot tool handlers for Autopilot execution."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from g_gremlin import hubspot_pack
from g_gremlin import hubspot_workflows
from g_gremlin.hubspot import HubSpotClient

from .contracts import PlanStep
from .tools import AutopilotToolDefinition


def handle_hubspot_pack_plan(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    pack_dir = args.get("pack_dir")
    if not pack_dir:
        raise RuntimeError("Missing pack_dir for hubspot pack plan.")
    client = _get_hubspot_client(args)
    plan = hubspot_pack.plan_pack(client, Path(pack_dir))
    return {"targets": [], "before": None, "after": _safe_to_dict(plan)}


def handle_hubspot_pack_apply(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    pack_dir = args.get("pack_dir")
    if not pack_dir:
        raise RuntimeError("Missing pack_dir for hubspot pack apply.")
    client = _get_hubspot_client(args)
    result = hubspot_pack.apply_pack(
        client,
        Path(pack_dir),
        None,
        allow_delete_stages=bool(args.get("allow_delete_stages", False)),
        allow_archive_properties=bool(args.get("allow_archive_properties", False)),
        allow_delete_workflows=bool(args.get("allow_delete_workflows", False)),
        replan=bool(args.get("replan", False)),
        plan_path=Path(args["plan_path"]) if args.get("plan_path") else None,
        expected_plan_hash=str(args.get("plan_hash") or "").strip() or None,
    )
    before = {"plan_path": args.get("plan_path"), "plan_hash": args.get("plan_hash")}
    return {"targets": [], "before": before, "after": _safe_to_dict(result)}


def handle_hubspot_pack_drift(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    pack_dir = args.get("pack_dir")
    if not pack_dir:
        raise RuntimeError("Missing pack_dir for hubspot pack drift.")
    client = _get_hubspot_client(args)
    drift = hubspot_pack.compute_drift(client, Path(pack_dir))
    return {"targets": [], "before": None, "after": _safe_to_dict(drift)}


def handle_hubspot_workflow_apply(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    workflows_dir = args.get("workflows_dir") or args.get("flows_dir") or args.get("dir")
    if not workflows_dir:
        raise RuntimeError("Missing workflows_dir for hubspot workflow apply.")
    workflows_path = Path(workflows_dir)
    client = _get_hubspot_client(args)
    backup_dir = Path(args.get("backup_dir") or (workflows_path / "backups"))

    successes: list[dict[str, Any]] = []
    failures: list[dict[str, Any]] = []

    for flow_path in sorted(workflows_path.glob("*.json")):
        try:
            payload = hubspot_workflows.load_flow_file(flow_path)
            flow_id = hubspot_workflows.resolve_flow_id(payload)
            if flow_id:
                remote = hubspot_workflows.get_flow(client, str(flow_id))
                backup = hubspot_workflows.backup_flow(str(flow_id), remote, backup_dir)
                hubspot_workflows.validate_flow_payload(payload, mode="update")
                result = hubspot_workflows.update_flow(client, str(flow_id), payload)
                successes.append(
                    {
                        "file": str(flow_path),
                        "action": "update",
                        "flow_id": flow_id,
                        "backup": str(backup),
                        "result": result,
                    }
                )
            else:
                hubspot_workflows.validate_flow_payload(payload, mode="create")
                result = hubspot_workflows.create_flow(client, payload)
                new_id = result.get("id") or result.get("flowId")
                successes.append(
                    {
                        "file": str(flow_path),
                        "action": "create",
                        "flow_id": new_id,
                        "result": result,
                    }
                )
        except Exception as exc:
            failures.append({"file": str(flow_path), "error": str(exc)})

    return {
        "targets": [],
        "before": {"backup_dir": str(backup_dir)},
        "after": {"successes": successes, "failures": failures},
    }


def handle_hubspot_associate_batch(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    from_type = args.get("from_type")
    to_type = args.get("to_type")
    if not from_type or not to_type:
        raise RuntimeError("Missing from_type or to_type for hubspot association batch.")

    mappings = _load_mappings(args)
    if not mappings:
        return {"targets": [], "before": None, "after": {"status": "no_mappings"}}

    client = _get_hubspot_client(args)
    from_ids = sorted({str(pair[0]) for pair in mappings})
    existing = client.read_associations_batch(str(from_type), str(to_type), from_ids)

    to_create = [
        (str(from_id), str(to_id))
        for from_id, to_id in mappings
        if str(to_id) not in (existing.get(str(from_id)) or [])
    ]

    result = client.associate_batch(str(from_type), str(to_type), to_create)
    rollback = {"from_type": from_type, "to_type": to_type, "mappings": to_create}
    return {
        "targets": [],
        "before": {"existing": existing},
        "after": {"result": _safe_to_dict(result), "rollback": rollback},
    }


def _get_hubspot_client(args: dict[str, Any]) -> HubSpotClient:
    profile = args.get("hubspot_profile") or args.get("profile")
    return HubSpotClient(profile=profile)


def _load_mappings(args: dict[str, Any]) -> list[tuple[str, str]]:
    mappings = args.get("mappings")
    if mappings is None:
        mappings_path = args.get("mappings_path")
        if mappings_path:
            data = Path(str(mappings_path)).read_text(encoding="utf-8")
            mappings = json.loads(data)
    if not mappings:
        return []
    normalized: list[tuple[str, str]] = []
    for item in mappings:
        from_id = None
        to_id = None
        if isinstance(item, (list, tuple)) and len(item) >= 2:
            from_id, to_id = item[0], item[1]
        elif isinstance(item, dict):
            from_id = item.get("from_id") or item.get("fromId") or item.get("from")
            to_id = item.get("to_id") or item.get("toId") or item.get("to")
            if isinstance(from_id, dict):
                from_id = from_id.get("id")
            if isinstance(to_id, dict):
                to_id = to_id.get("id")
        if from_id and to_id:
            normalized.append((str(from_id), str(to_id)))
    return normalized


def _safe_to_dict(value: Any) -> Any:
    if hasattr(value, "to_dict") and callable(getattr(value, "to_dict")):
        try:
            return value.to_dict()
        except Exception:
            return value
    return value


__all__ = [
    "handle_hubspot_pack_plan",
    "handle_hubspot_pack_apply",
    "handle_hubspot_pack_drift",
    "handle_hubspot_workflow_apply",
    "handle_hubspot_associate_batch",
]
