"""Salesforce tool handlers for Autopilot execution."""

from __future__ import annotations

import os
from pathlib import Path
from typing import Any

from g_gremlin import sfdc as sfdc_helpers
from g_gremlin import sfdc_metadata_pack

from .contracts import PlanStep
from .slack_blocks import build_notify_blocks
from .slack_notify import create_client as slack_create_client
from .tools import AutopilotToolDefinition


def _as_list(value: Any) -> list[str]:
    if value is None:
        return []
    if isinstance(value, list):
        return [str(item) for item in value if item not in (None, "")]
    return [str(value)]


def _extract_targets(step: PlanStep) -> list[str]:
    args = step.args or {}
    for key in ("record_ids", "user_ids", "targets", "ids"):
        value = args.get(key)
        if isinstance(value, list):
            return [str(item) for item in value if item not in (None, "")]
    if isinstance(args.get("records"), list):
        return [
            str(row.get("record_id"))
            for row in args.get("records", [])
            if row.get("record_id")
        ]
    return []


def _extract_from_details(step: PlanStep, keys: list[str]) -> list[str]:
    details = step.args.get("action_details") or {}
    if not isinstance(details, dict):
        return []
    values: list[str] = []
    for key in keys:
        values.extend(_as_list(details.get(key)))
    return values


def _task_target_field_name(sobject: str) -> str:
    if (sobject or "").lower() in ("lead", "contact"):
        return "WhoId"
    return "WhatId"


def _extract_task_text(step: PlanStep, key: str) -> str | None:
    value = step.args.get(key)
    if value:
        return str(value)
    details = step.args.get("action_details") or {}
    if isinstance(details, dict):
        raw = details.get(key)
        if raw:
            return str(raw)
    return None


def _summarize_slack_records(records: list[dict[str, Any]]) -> list[str]:
    usernames: list[str] = []
    reason_codes: set[str] = set()
    for row in records:
        if not isinstance(row, dict):
            continue
        fields = row.get("fields") if isinstance(row.get("fields"), dict) else {}
        candidate = fields.get("username") or fields.get("name") or row.get("record_id")
        if candidate:
            value = str(candidate)
            if value not in usernames:
                usernames.append(value)
        reason = row.get("reason_code")
        if reason:
            reason_codes.add(str(reason))
        extra_codes = fields.get("reason_codes")
        if isinstance(extra_codes, list):
            for code in extra_codes:
                if code:
                    reason_codes.add(str(code))

    details: list[str] = []
    if usernames:
        top_users = ", ".join(usernames[:3])
        suffix = " +" if len(usernames) > 3 else ""
        details.append(f"Users: {top_users}{suffix}")
    if reason_codes:
        details.append(f"Reasons: {', '.join(sorted(reason_codes))}")
    return details


def _build_task_values(
    *,
    subject: str,
    description: str | None,
    due_date: str | None,
    owner_id: str | None,
    record_id: str,
    sobject: str,
    row_fields: dict[str, Any] | None = None,
) -> dict[str, Any]:
    values: dict[str, Any] = {
        "Subject": subject,
        "Status": "Not Started",
        "Priority": "Normal",
    }
    if description:
        values["Description"] = description
    if due_date:
        values["ActivityDate"] = due_date
    if owner_id:
        values["OwnerId"] = owner_id
    elif row_fields:
        row_owner = row_fields.get("OwnerId") or row_fields.get("owner_id")
        if row_owner:
            values["OwnerId"] = row_owner

    target_field = _task_target_field_name(sobject)
    values[target_field] = record_id
    return values


def handle_assign_permission_sets(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    user_ids = _as_list(args.get("user_ids") or args.get("record_ids"))
    permission_set_ids = _as_list(args.get("permission_set_ids") or args.get("permission_set_id"))
    if not permission_set_ids:
        permission_set_ids = _as_list(args.get("permission_set_names"))
    if not permission_set_ids:
        permission_set_ids = _extract_from_details(
            step,
            ["permission_set_ids", "permission_set_id", "permission_set_names"],
        )

    if not user_ids or not permission_set_ids:
        raise RuntimeError("Missing user_ids or permission_set_ids for permission set assignment.")

    org_alias = args.get("org_alias")
    successes: list[dict[str, Any]] = []
    failures: list[dict[str, Any]] = []

    for user_id in user_ids:
        for perm_id in permission_set_ids:
            try:
                result = sfdc_helpers.create_record(
                    "PermissionSetAssignment",
                    {"AssigneeId": user_id, "PermissionSetId": perm_id},
                    org_alias=org_alias,
                )
                successes.append({
                    "user_id": user_id,
                    "permission_set_id": perm_id,
                    "result": result,
                })
            except Exception as exc:
                failures.append({
                    "user_id": user_id,
                    "permission_set_id": perm_id,
                    "error": str(exc),
                })

    targets = [f"{user_id}:{perm_id}" for user_id in user_ids for perm_id in permission_set_ids]
    if failures:
        error = RuntimeError(
            f"permission_set_assignment_failed successes={len(successes)} failures={len(failures)}"
        )
        error.successes = successes  # type: ignore[attr-defined]
        error.failures = failures  # type: ignore[attr-defined]
        raise error

    return {
        "targets": targets,
        "before": {"user_ids": user_ids, "permission_set_ids": permission_set_ids},
        "after": {"assignments": successes},
    }


def handle_assign_permission_set_groups(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    user_ids = _as_list(args.get("user_ids") or args.get("record_ids"))
    group_ids = _as_list(args.get("permission_set_group_ids") or args.get("permission_set_group_id"))
    if not group_ids:
        group_ids = _as_list(args.get("permission_set_group_names"))
    if not group_ids:
        group_ids = _extract_from_details(
            step,
            ["permission_set_group_ids", "permission_set_group_id", "permission_set_group_names"],
        )

    if not user_ids or not group_ids:
        raise RuntimeError("Missing user_ids or permission_set_group_ids for group assignment.")

    org_alias = args.get("org_alias")
    successes: list[dict[str, Any]] = []
    failures: list[dict[str, Any]] = []

    for user_id in user_ids:
        for group_id in group_ids:
            try:
                result = sfdc_helpers.create_record(
                    "PermissionSetGroupAssignment",
                    {"AssigneeId": user_id, "PermissionSetGroupId": group_id},
                    org_alias=org_alias,
                )
                successes.append({
                    "user_id": user_id,
                    "permission_set_group_id": group_id,
                    "result": result,
                })
            except Exception as exc:
                failures.append({
                    "user_id": user_id,
                    "permission_set_group_id": group_id,
                    "error": str(exc),
                })

    targets = [f"{user_id}:{group_id}" for user_id in user_ids for group_id in group_ids]
    if failures:
        error = RuntimeError(
            f"permission_set_group_assignment_failed successes={len(successes)} failures={len(failures)}"
        )
        error.successes = successes  # type: ignore[attr-defined]
        error.failures = failures  # type: ignore[attr-defined]
        raise error

    return {
        "targets": targets,
        "before": {"user_ids": user_ids, "permission_set_group_ids": group_ids},
        "after": {"assignments": successes},
    }


def handle_add_to_queue_group(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    user_ids = _as_list(args.get("user_ids") or args.get("record_ids"))
    group_ids = _as_list(args.get("group_ids") or args.get("queue_group_ids"))
    if not group_ids:
        group_ids = _as_list(args.get("group_id") or args.get("queue_group_id"))
    if not group_ids:
        group_ids = _as_list(args.get("group_names") or args.get("queue_names"))
    if not group_ids:
        group_ids = _extract_from_details(
            step,
            ["group_ids", "group_id", "queue_group_ids", "queue_group_id", "group_names", "queue_names"],
        )

    if not user_ids or not group_ids:
        raise RuntimeError("Missing user_ids or group_ids for queue/group membership.")

    org_alias = args.get("org_alias")
    successes: list[dict[str, Any]] = []
    failures: list[dict[str, Any]] = []

    for user_id in user_ids:
        for group_id in group_ids:
            try:
                result = sfdc_helpers.create_record(
                    "GroupMember",
                    {"GroupId": group_id, "UserOrGroupId": user_id},
                    org_alias=org_alias,
                )
                successes.append({
                    "user_id": user_id,
                    "group_id": group_id,
                    "result": result,
                })
            except Exception as exc:
                failures.append({
                    "user_id": user_id,
                    "group_id": group_id,
                    "error": str(exc),
                })

    targets = [f"{user_id}:{group_id}" for user_id in user_ids for group_id in group_ids]
    if failures:
        error = RuntimeError(
            f"group_membership_failed successes={len(successes)} failures={len(failures)}"
        )
        error.successes = successes  # type: ignore[attr-defined]
        error.failures = failures  # type: ignore[attr-defined]
        raise error

    return {
        "targets": targets,
        "before": {"user_ids": user_ids, "group_ids": group_ids},
        "after": {"memberships": successes},
    }


def handle_forecast_assign_user_to_role(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    user_ids = _as_list(args.get("user_ids") or args.get("record_ids"))
    role_ids = _as_list(args.get("role_ids") or args.get("role_id") or args.get("forecast_role_id"))
    if not role_ids:
        role_ids = _extract_from_details(
            step,
            ["role_ids", "role_id", "forecast_role_id", "forecast_role_ids"],
        )
    assignments = args.get("assignments") or args.get("role_assignments")
    if assignments and isinstance(assignments, list):
        user_ids = [str(item.get("user_id")) for item in assignments if item.get("user_id")]
        role_ids = [str(item.get("role_id")) for item in assignments if item.get("role_id")]

    if not user_ids or not role_ids:
        raise RuntimeError("Missing user_ids or role_ids for forecast assignment.")

    if len(role_ids) not in (1, len(user_ids)):
        raise RuntimeError("role_ids count must be 1 or match user_ids count.")

    org_alias = args.get("org_alias")
    sobject = args.get("sobject") or "User"
    role_field = args.get("role_field") or "ForecastingRoleId"

    successes: list[dict[str, Any]] = []
    failures: list[dict[str, Any]] = []
    for idx, user_id in enumerate(user_ids):
        role_id = role_ids[0] if len(role_ids) == 1 else role_ids[idx]
        try:
            result = sfdc_helpers.update_record(
                sobject,
                user_id,
                {role_field: role_id},
                org_alias=org_alias,
            )
            successes.append({
                "user_id": user_id,
                "role_id": role_id,
                "result": result,
            })
        except Exception as exc:
            failures.append({
                "user_id": user_id,
                "role_id": role_id,
                "error": str(exc),
            })

    targets = [f"{user_id}:{role_ids[0] if len(role_ids) == 1 else role_ids[idx]}" for idx, user_id in enumerate(user_ids)]
    if failures:
        error = RuntimeError(
            f"forecast_role_assignment_failed successes={len(successes)} failures={len(failures)}"
        )
        error.successes = successes  # type: ignore[attr-defined]
        error.failures = failures  # type: ignore[attr-defined]
        raise error

    return {
        "targets": targets,
        "before": {"user_ids": user_ids, "role_ids": role_ids},
        "after": {"assignments": successes},
    }


def handle_sfdc_user_deactivate(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    user_ids = _as_list(args.get("user_ids") or args.get("record_ids"))
    if not user_ids:
        raise RuntimeError("Missing user_ids for user deactivation.")

    org_alias = args.get("org_alias")
    escaped = ", ".join(f"'{sfdc_helpers.escape_soql_value(i)}'" for i in user_ids)
    precheck_query = (
        "SELECT Id, IsActive, Profile.Name "
        "FROM User "
        f"WHERE Id IN ({escaped})"
    )
    rows = sfdc_helpers.run_soql(precheck_query, org_alias=org_alias)

    found_ids = {row.get("Id") for row in rows if row.get("Id")}
    missing = [user_id for user_id in user_ids if user_id not in found_ids]
    if missing:
        raise RuntimeError(f"Missing users for deactivation: {missing}")

    admin_users = []
    for row in rows:
        profile_name = str(row.get("Profile.Name") or "")
        if "system administrator" in profile_name.lower():
            admin_users.append(row.get("Id"))
    if admin_users:
        raise RuntimeError(f"Cannot deactivate System Administrators: {admin_users}")

    successes: list[dict[str, Any]] = []
    failures: list[dict[str, Any]] = []
    for row in rows:
        user_id = row.get("Id")
        is_active = row.get("IsActive")
        if not user_id:
            continue
        if str(is_active).lower() in ("false", "0"):
            successes.append({"user_id": user_id, "result": {"status": "already_inactive"}})
            continue
        try:
            result = sfdc_helpers.update_record(
                "User",
                user_id,
                {"IsActive": False},
                org_alias=org_alias,
            )
            successes.append({"user_id": user_id, "result": result})
        except Exception as exc:
            failures.append({"user_id": user_id, "error": str(exc)})

    if failures:
        error = RuntimeError(
            f"user_deactivation_failed successes={len(successes)} failures={len(failures)}"
        )
        error.successes = successes  # type: ignore[attr-defined]
        error.failures = failures  # type: ignore[attr-defined]
        raise error

    postcheck_query = (
        "SELECT Id, IsActive "
        "FROM User "
        f"WHERE Id IN ({escaped})"
    )
    post_rows = sfdc_helpers.run_soql(postcheck_query, org_alias=org_alias)
    still_active = [row.get("Id") for row in post_rows if str(row.get("IsActive")).lower() in ("true", "1")]
    postcheck_warning = None
    if still_active:
        postcheck_warning = f"Users still active: {still_active}"

    return {
        "targets": list(user_ids),
        "before": {"user_ids": list(user_ids)},
        "after": {
            "deactivated": successes,
            "failed": failures,
            "postcheck_warning": postcheck_warning,
        },
    }


def handle_sfdc_record_update(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    org_alias = args.get("org_alias")
    sobject = args.get("sobject") or args.get("target_sobject") or "Opportunity"

    updates: list[tuple[str, dict[str, Any]]] = []
    record_id = args.get("record_id")
    fields = args.get("fields")
    if record_id and isinstance(fields, dict):
        updates.append((str(record_id), fields))
    elif isinstance(args.get("records"), list):
        for row in args.get("records") or []:
            if not isinstance(row, dict):
                continue
            rid = row.get("record_id")
            row_fields = row.get("fields") if isinstance(row.get("fields"), dict) else {}
            if isinstance(row_fields, dict) and "proposed_diffs" in row_fields:
                row_fields = row_fields.get("proposed_diffs") or {}
            if rid and isinstance(row_fields, dict):
                updates.append((str(rid), row_fields))

    if not updates:
        raise RuntimeError("Missing record updates for sfdc.record.update.")

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

    for rid, fields_map in updates:
        if not fields_map:
            continue
        targets.append(rid)
        before_snapshot = None
        try:
            escaped_id = sfdc_helpers.escape_soql_value(rid)
            select_fields = ", ".join(["Id"] + list(fields_map.keys()))
            query = f"SELECT {select_fields} FROM {sobject} WHERE Id = '{escaped_id}'"
            rows = sfdc_helpers.run_soql(query, org_alias=org_alias)
            before_snapshot = rows[0] if rows else None
        except Exception:
            before_snapshot = None

        try:
            result = sfdc_helpers.update_record(sobject, rid, fields_map, org_alias=org_alias)
            successes.append({
                "record_id": rid,
                "before": before_snapshot,
                "result": result,
            })
        except Exception as exc:
            failures.append({
                "record_id": rid,
                "error": str(exc),
            })

    if failures:
        error = RuntimeError(
            f"sfdc_record_update_failed successes={len(successes)} failures={len(failures)}"
        )
        error.successes = successes  # type: ignore[attr-defined]
        error.failures = failures  # type: ignore[attr-defined]
        raise error

    return {
        "targets": targets,
        "before": {"records": [s.get("before") for s in successes]},
        "after": {"updates": successes},
    }


def handle_sfdc_record_reassign_owner(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    record_ids = _as_list(args.get("record_ids") or args.get("targets") or args.get("ids"))
    sobject = args.get("sobject") or args.get("target_sobject")
    new_owner_id = args.get("new_owner_id") or args.get("owner_id")
    if not record_ids or not sobject or not new_owner_id:
        details = args.get("action_details") or {}
        if not new_owner_id and isinstance(details, dict):
            new_owner_id = details.get("new_owner_id") or details.get("default_owner_id")
        if not sobject and isinstance(details, dict):
            sobject = details.get("sobject")
    if not record_ids or not sobject or not new_owner_id:
        raise RuntimeError("Missing sobject, record_ids, or new_owner_id for reassignment.")

    from g_gremlin.scout.field_mapping import FieldMapper

    org_alias = args.get("org_alias")
    mapper = FieldMapper("salesforce")
    native_sobject = mapper.native_object(str(sobject)) or str(sobject)

    owner_query = (
        "SELECT Id, IsActive, Profile.Name "
        "FROM User "
        f"WHERE Id = '{sfdc_helpers.escape_soql_value(new_owner_id)}'"
    )
    owner_rows = sfdc_helpers.run_soql(owner_query, org_alias=org_alias)
    if not owner_rows:
        queue_query = (
            "SELECT Id, Type "
            "FROM Group "
            f"WHERE Id = '{sfdc_helpers.escape_soql_value(new_owner_id)}' AND Type = 'Queue'"
        )
        queue_rows = sfdc_helpers.run_soql(queue_query, org_alias=org_alias)
        if not queue_rows:
            raise RuntimeError(f"Owner {new_owner_id} not found (user or queue).")
    else:
        if str(owner_rows[0].get("IsActive")).lower() in ("false", "0"):
            raise RuntimeError(f"User {new_owner_id} is inactive.")

    original_owners: dict[str, str] = {}
    if len(record_ids) <= 100:
        escaped = ", ".join(f"'{sfdc_helpers.escape_soql_value(i)}'" for i in record_ids)
        snapshot_query = (
            f"SELECT Id, OwnerId FROM {native_sobject} WHERE Id IN ({escaped})"
        )
        snapshot_rows = sfdc_helpers.run_soql(snapshot_query, org_alias=org_alias)
        original_owners = {
            str(row.get("Id")): str(row.get("OwnerId"))
            for row in snapshot_rows
            if row.get("Id") and row.get("OwnerId")
        }

    successes: list[dict[str, Any]] = []
    failures: list[dict[str, Any]] = []
    for record_id in record_ids:
        try:
            result = sfdc_helpers.update_record(
                native_sobject,
                record_id,
                {"OwnerId": new_owner_id},
                org_alias=org_alias,
            )
            successes.append({"record_id": record_id, "result": result})
        except Exception as exc:
            failures.append({"record_id": record_id, "error": str(exc)})

    if failures:
        error = RuntimeError(
            f"record_reassignment_failed successes={len(successes)} failures={len(failures)}"
        )
        error.successes = successes  # type: ignore[attr-defined]
        error.failures = failures  # type: ignore[attr-defined]
        raise error

    escaped = ", ".join(f"'{sfdc_helpers.escape_soql_value(i)}'" for i in record_ids)
    post_query = f"SELECT Id, OwnerId FROM {native_sobject} WHERE Id IN ({escaped})"
    post_rows = sfdc_helpers.run_soql(post_query, org_alias=org_alias)
    not_reassigned = [
        row.get("Id")
        for row in post_rows
        if row.get("Id") and str(row.get("OwnerId")) != str(new_owner_id)
    ]
    postcheck_warning = None
    if not_reassigned:
        postcheck_warning = f"Records not reassigned: {not_reassigned}"

    return {
        "targets": list(record_ids),
        "before": {"record_ids": list(record_ids), "original_owners": original_owners},
        "after": {
            "reassigned": successes,
            "failed": failures,
            "postcheck_warning": postcheck_warning,
            "rollback_data": {"original_owners": original_owners, "sobject": native_sobject}
            if original_owners
            else None,
        },
    }


def handle_tasks_create(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    org_alias = args.get("org_alias")
    subject = _extract_task_text(step, "subject") or "Autopilot follow-up"
    description = _extract_task_text(step, "description")
    due_date = _extract_task_text(step, "due_date")
    owner_id = _extract_task_text(step, "owner_id")

    records = args.get("records") if isinstance(args.get("records"), list) else []
    record_ids = _as_list(args.get("record_ids"))
    sobject = args.get("sobject") or args.get("target_sobject") or ""

    if not records and not record_ids:
        raise RuntimeError("Missing record targets for task creation.")

    if records:
        targets = [str(row.get("record_id")) for row in records if row.get("record_id")]
    else:
        targets = list(record_ids)

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

    if records:
        for row in records:
            record_id = row.get("record_id")
            row_sobject = row.get("sobject") or sobject
            if not record_id:
                continue
            values = _build_task_values(
                subject=subject,
                description=description,
                due_date=due_date,
                owner_id=owner_id,
                record_id=record_id,
                sobject=str(row_sobject),
                row_fields=row.get("fields") if isinstance(row.get("fields"), dict) else None,
            )

            try:
                result = sfdc_helpers.create_record("Task", values, org_alias=org_alias)
                successes.append({"record_id": record_id, "sobject": row_sobject, "result": result})
            except Exception as exc:
                failures.append({"record_id": record_id, "sobject": row_sobject, "error": str(exc)})
    else:
        for record_id in record_ids:
            values = _build_task_values(
                subject=subject,
                description=description,
                due_date=due_date,
                owner_id=owner_id,
                record_id=record_id,
                sobject=str(sobject),
                row_fields=None,
            )
            try:
                result = sfdc_helpers.create_record("Task", values, org_alias=org_alias)
                successes.append({"record_id": record_id, "sobject": sobject, "result": result})
            except Exception as exc:
                failures.append({"record_id": record_id, "sobject": sobject, "error": str(exc)})

    if failures:
        error = RuntimeError(f"task_create_failed successes={len(successes)} failures={len(failures)}")
        error.successes = successes  # type: ignore[attr-defined]
        error.failures = failures  # type: ignore[attr-defined]
        raise error

    return {
        "targets": targets,
        "before": None,
        "after": {"created": successes},
    }


def handle_slack_notify(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    args = step.args or {}
    details = args.get("action_details") if isinstance(args.get("action_details"), dict) else {}

    channel = args.get("channel") or details.get("channel") or os.getenv("G_GREMLIN_SLACK_CHANNEL")
    if not channel:
        raise RuntimeError("Missing Slack channel for notification.")

    message = args.get("message") or details.get("message")
    severity = args.get("severity") or details.get("severity") or "info"
    title = args.get("title") or details.get("title") or "Integration Health Alert"

    records = args.get("records") if isinstance(args.get("records"), list) else []
    if not message:
        scout_name = args.get("scout_name") or "autopilot"
        count = len([row for row in records if isinstance(row, dict)])
        message = f"{scout_name} alert: {count} finding(s) flagged."

    details_lines = _summarize_slack_records(records)
    blocks = build_notify_blocks(
        title=str(title),
        message=str(message),
        severity=str(severity),
        details=details_lines,
    )

    bot_token = args.get("bot_token") or os.getenv("G_GREMLIN_SLACK_BOT_TOKEN")
    if not bot_token:
        raise RuntimeError("Missing Slack bot token for notification.")

    client = slack_create_client(str(bot_token))
    response = client.chat_postMessage(channel=channel, text=str(message), **blocks)

    return {
        "targets": [str(channel)],
        "before": None,
        "after": {
            "status": "sent",
            "channel": channel,
            "ts": response.get("ts") if isinstance(response, dict) else None,
        },
    }


def handle_scout_user_provisioning_parity(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    return {
        "targets": _extract_targets(step),
        "before": None,
        "after": {"status": "executed"},
    }


def handle_scout_forecast_hierarchy_drift(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    return {
        "targets": _extract_targets(step),
        "before": None,
        "after": {"status": "executed"},
    }


def handle_scout_automation_failure_impact(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    return {
        "targets": _extract_targets(step),
        "before": None,
        "after": {"status": "executed"},
    }


def handle_scout_integration_health(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    return {
        "targets": _extract_targets(step),
        "before": None,
        "after": {"status": "executed"},
    }


def handle_scout_lead_to_account_match_health(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    return {
        "targets": _extract_targets(step),
        "before": None,
        "after": {"status": "executed"},
    }


def handle_scout_workflow_impact(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    return {
        "targets": _extract_targets(step),
        "before": None,
        "after": {"status": "executed"},
    }


def handle_scout_license_utilization(
    step: PlanStep,
    tool_def: AutopilotToolDefinition | None,
) -> dict[str, Any]:
    return {
        "targets": _extract_targets(step),
        "before": None,
        "after": {"status": "executed"},
    }


def handle_architect_metadata_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 metadata pack plan.")
    org_alias = args.get("org_alias")
    plan = sfdc_metadata_pack.plan_pack(Path(pack_dir), org_alias=org_alias)
    return {
        "targets": [],
        "before": None,
        "after": _safe_to_dict(plan),
    }


def handle_architect_metadata_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 metadata pack apply.")
    org_alias = args.get("org_alias")
    yes = bool(args.get("yes", True))
    check_only = bool(args.get("check_only", False))
    replan = bool(args.get("replan", False))
    plan_path = args.get("plan_path")
    plan_path_value = Path(plan_path) if plan_path else None
    result = sfdc_metadata_pack.apply_pack(
        Path(pack_dir),
        org_alias=org_alias,
        yes=yes,
        check_only=check_only,
        replan=replan,
        plan_path=plan_path_value,
    )
    return {
        "targets": [],
        "before": None,
        "after": _safe_to_dict(result),
    }


def handle_architect_metadata_pack_verify(
    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 metadata pack verify.")
    org_alias = args.get("org_alias")
    report = sfdc_metadata_pack.verify_pack(Path(pack_dir), org_alias=org_alias)
    return {
        "targets": [],
        "before": None,
        "after": _safe_to_dict(report),
    }


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_assign_permission_sets",
    "handle_assign_permission_set_groups",
    "handle_add_to_queue_group",
    "handle_forecast_assign_user_to_role",
    "handle_sfdc_record_update",
    "handle_sfdc_record_reassign_owner",
    "handle_sfdc_user_deactivate",
    "handle_scout_forecast_hierarchy_drift",
    "handle_scout_automation_failure_impact",
    "handle_scout_integration_health",
    "handle_scout_lead_to_account_match_health",
    "handle_scout_workflow_impact",
    "handle_scout_license_utilization",
    "handle_scout_user_provisioning_parity",
    "handle_architect_metadata_pack_plan",
    "handle_architect_metadata_pack_apply",
    "handle_architect_metadata_pack_verify",
    "handle_slack_notify",
    "handle_tasks_create",
]
