from __future__ import annotations

import json
import time
from datetime import datetime, timezone
from functools import partial
from pathlib import Path
from typing import Any, Dict, List, Sequence, Tuple

from g_gremlin import dedup as dedup_helpers
from g_gremlin import sfdc as sfdc_helpers
from g_gremlin import sfdc_dedupe_apply


def _resolved_target(target: str | None) -> str | None:
    return str(target or sfdc_helpers.get_configured_org() or "").strip() or None


def _coerce_sources(sources: Sequence[Dict[str, Any]]) -> List[Tuple[str, Path]]:
    source_specs: List[Tuple[str, Path]] = []
    for entry in sources:
        if not isinstance(entry, dict):
            raise ValueError("Each dedupe source must be an object with object_type and path.")
        object_type = str(
            entry.get("object_type")
            or entry.get("object")
            or entry.get("name")
            or ""
        ).strip()
        path_value = str(entry.get("path") or "").strip()
        if not object_type or not path_value:
            raise ValueError("Each dedupe source requires object_type and path.")
        source_specs.append((object_type, Path(path_value)))
    if not source_specs:
        raise ValueError("At least one dedupe source is required.")
    return source_specs


def _operation_type(entry: Dict[str, Any]) -> str:
    return str(entry.get("operation_type") or "merge").strip().lower() or "merge"


def _operation_mix(entries: Sequence[Dict[str, Any]]) -> Dict[str, int]:
    counts: Dict[str, int] = {}
    for entry in entries:
        if not isinstance(entry, dict):
            continue
        op_type = _operation_type(entry)
        counts[op_type] = counts.get(op_type, 0) + 1
    return dict(sorted(counts.items()))


def _parse_bool(value: Any) -> bool:
    return str(value or "").strip().lower() == "true"


def _validate_plan_hash(plan_text: str, plan_hash: str, *, tool_name: str) -> str:
    actual = sfdc_dedupe_apply.plan_digest(plan_text)
    expected = str(plan_hash or "").strip()
    if not expected:
        raise ValueError(f"plan_hash is required for {tool_name}.")
    if actual != expected:
        raise ValueError(
            f"plan_hash mismatch for {tool_name}. Re-run the planning tool and use the returned plan_hash."
        )
    return actual


def _load_json_object(path: Path, *, label: str) -> Dict[str, Any]:
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception as exc:
        raise ValueError(f"Invalid {label} JSON: {exc}") from exc
    if not isinstance(payload, dict):
        raise ValueError(f"{label} JSON must contain an object.")
    return payload


def _quoted_ids(values: Sequence[str]) -> str:
    return ", ".join(f"'{sfdc_helpers.escape_soql_value(value)}'" for value in values if str(value).strip())


def _normalize_rows_by_id(rows: Sequence[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
    normalized: Dict[str, Dict[str, Any]] = {}
    for row in rows:
        if not isinstance(row, dict):
            continue
        row_id = str(row.get("Id") or "").strip()
        if row_id:
            normalized[row_id] = dict(row)
    return normalized


def _verify_merge_operation(entry: Dict[str, Any], *, org_alias: str) -> Dict[str, Any]:
    object_type = str(entry.get("object_type") or "").strip()
    master_id = str(entry.get("master_id") or "").strip()
    victim_ids = [str(value).strip() for value in (entry.get("victim_ids") or []) if str(value).strip()]
    result = {
        "operation_id": entry.get("operation_id"),
        "operation_type": "merge",
        "cluster_id": entry.get("cluster_id"),
        "status": "success",
        "expected": {
            "object_type": object_type,
            "master_id": master_id,
            "victim_ids": victim_ids,
        },
        "actual": {},
        "mismatches": [],
    }
    if not object_type or not master_id:
        result["status"] = "error"
        result["mismatches"].append("missing_object_type_or_master_id")
        return result

    master_rows = sfdc_helpers.run_soql(
        f"SELECT Id FROM {object_type} WHERE Id = '{sfdc_helpers.escape_soql_value(master_id)}' LIMIT 1",
        org_alias=org_alias,
    )
    result["actual"]["master_found"] = bool(master_rows)
    if not master_rows:
        result["status"] = "error"
        result["mismatches"].append("master_not_found")

    victim_rows: List[Dict[str, Any]] = []
    if victim_ids:
        victim_rows = sfdc_helpers.run_soql(
            f"SELECT Id, IsDeleted FROM {object_type} WHERE Id IN ({_quoted_ids(victim_ids)}) ALL ROWS",
            org_alias=org_alias,
        )
    victim_map = _normalize_rows_by_id(victim_rows)
    victim_states: Dict[str, Dict[str, Any]] = {}
    for victim_id in victim_ids:
        victim_row = victim_map.get(victim_id) or {}
        is_deleted = _parse_bool(victim_row.get("IsDeleted"))
        victim_states[victim_id] = {
            "found": bool(victim_row),
            "is_deleted": is_deleted,
        }
        if victim_row and not is_deleted:
            result["status"] = "error"
            result["mismatches"].append(f"victim_not_deleted:{victim_id}")
    result["actual"]["victims"] = victim_states
    return result


def _verify_convert_lead_operation(entry: Dict[str, Any], *, org_alias: str) -> Dict[str, Any]:
    lead_id = str(entry.get("lead_id") or "").strip()
    converted_contact_id = str(entry.get("converted_contact_id") or "").strip()
    converted_account_id = str(entry.get("converted_account_id") or "").strip()
    converted_status = str(entry.get("converted_status") or "").strip()
    do_not_create_opportunity = bool(entry.get("do_not_create_opportunity", True))
    result = {
        "operation_id": entry.get("operation_id"),
        "operation_type": "convert_lead",
        "cluster_id": entry.get("cluster_id"),
        "status": "success",
        "expected": {
            "lead_id": lead_id,
            "converted_contact_id": converted_contact_id,
            "converted_account_id": converted_account_id,
            "converted_status": converted_status,
            "do_not_create_opportunity": do_not_create_opportunity,
        },
        "actual": {},
        "mismatches": [],
    }
    if not lead_id or not converted_contact_id:
        result["status"] = "error"
        result["mismatches"].append("missing_lead_id_or_converted_contact_id")
        return result

    lead_rows = sfdc_helpers.run_soql(
        (
            "SELECT Id, IsConverted, Status, ConvertedContactId, ConvertedAccountId, ConvertedOpportunityId "
            f"FROM Lead WHERE Id = '{sfdc_helpers.escape_soql_value(lead_id)}' ALL ROWS"
        ),
        org_alias=org_alias,
    )
    lead_row = lead_rows[0] if lead_rows else {}
    result["actual"]["lead"] = lead_row
    if not lead_row:
        result["status"] = "error"
        result["mismatches"].append("lead_not_found")
        return result
    if not _parse_bool(lead_row.get("IsConverted")):
        result["status"] = "error"
        result["mismatches"].append("lead_not_converted")
    if str(lead_row.get("ConvertedContactId") or "").strip() != converted_contact_id:
        result["status"] = "error"
        result["mismatches"].append("converted_contact_id_mismatch")
    if converted_account_id and str(lead_row.get("ConvertedAccountId") or "").strip() != converted_account_id:
        result["status"] = "error"
        result["mismatches"].append("converted_account_id_mismatch")
    if converted_status and str(lead_row.get("Status") or "").strip() != converted_status:
        result["status"] = "error"
        result["mismatches"].append("converted_status_mismatch")
    if do_not_create_opportunity and str(lead_row.get("ConvertedOpportunityId") or "").strip():
        result["status"] = "error"
        result["mismatches"].append("unexpected_opportunity_created")

    contact_rows = sfdc_helpers.run_soql(
        (
            "SELECT Id, AccountId "
            f"FROM Contact WHERE Id = '{sfdc_helpers.escape_soql_value(converted_contact_id)}' LIMIT 1"
        ),
        org_alias=org_alias,
    )
    contact_row = contact_rows[0] if contact_rows else {}
    result["actual"]["contact"] = contact_row
    if not contact_row:
        result["status"] = "error"
        result["mismatches"].append("converted_contact_not_found")
    elif converted_account_id and str(contact_row.get("AccountId") or "").strip() != converted_account_id:
        result["status"] = "error"
        result["mismatches"].append("contact_account_id_mismatch")

    return result


def _fetch_duplicate_rules(org_alias: str | None) -> Dict[str, Any]:
    if not org_alias:
        return {"rules": [], "error": "No target org configured."}
    try:
        rows = sfdc_helpers.run_soql(
            (
                "SELECT Id, DeveloperName, MasterLabel, IsActive, SObjectType "
                "FROM DuplicateRule "
                "WHERE SObjectType IN ('Lead', 'Contact') "
                "ORDER BY SObjectType, DeveloperName"
            ),
            org_alias=org_alias,
        )
        return {"rules": rows, "error": ""}
    except Exception as exc:
        return {"rules": [], "error": str(exc)}


def dedupe_preflight(target: str | None = None) -> Dict[str, Any]:
    org_alias = _resolved_target(target)
    status = "ok"
    checks: List[Dict[str, Any]] = []

    identity: Dict[str, Any]
    if not org_alias:
        identity = {}
        checks.append({"name": "org_alias", "status": "error", "detail": "No Salesforce org is configured."})
        status = "error"
    else:
        try:
            identity = sfdc_helpers.get_org_identity(org_alias=org_alias)
            checks.append({"name": "org_identity", "status": "ok", "detail": org_alias})
        except Exception as exc:
            identity = {"error": str(exc)}
            checks.append({"name": "org_identity", "status": "error", "detail": str(exc)})
            status = "error"

    lead_status_rows: List[Dict[str, Any]] = []
    converted_statuses: List[str] = []
    if org_alias:
        try:
            lead_status_rows = sfdc_helpers.run_soql(
                "SELECT MasterLabel, IsConverted, SortOrder FROM LeadStatus ORDER BY SortOrder",
                org_alias=org_alias,
            )
            converted_statuses = [
                str(row.get("MasterLabel") or "").strip()
                for row in lead_status_rows
                if _parse_bool(row.get("IsConverted"))
            ]
            checks.append(
                {
                    "name": "lead_statuses",
                    "status": "ok",
                    "detail": f"{len(converted_statuses)} converted status value(s) found.",
                }
            )
        except Exception as exc:
            checks.append({"name": "lead_statuses", "status": "warn", "detail": str(exc)})
            if status == "ok":
                status = "warn"

    duplicate_rule_info = _fetch_duplicate_rules(org_alias)
    if duplicate_rule_info["error"]:
        checks.append({"name": "duplicate_rules", "status": "warn", "detail": duplicate_rule_info["error"]})
        if status == "ok":
            status = "warn"
    else:
        active_rule_count = sum(1 for row in duplicate_rule_info["rules"] if _parse_bool(row.get("IsActive")))
        checks.append(
            {
                "name": "duplicate_rules",
                "status": "ok",
                "detail": f"{active_rule_count} active Lead/Contact duplicate rule(s).",
            }
        )

    anonymous_apex = {"allowed": False, "detail": "No target org configured."}
    if org_alias:
        try:
            apex_payload = sfdc_helpers._run_anonymous_apex(
                "System.debug('gremlin_dedupe_preflight');",
                org_alias=org_alias,
            )
            result = apex_payload.get("result") if isinstance(apex_payload, dict) else {}
            compiled = bool((result or {}).get("compiled"))
            succeeded = bool((result or {}).get("success"))
            allowed = compiled and succeeded
            detail = "allowed" if allowed else str(
                (result or {}).get("exceptionMessage")
                or (result or {}).get("compileProblem")
                or "Anonymous Apex probe failed."
            )
            anonymous_apex = {"allowed": allowed, "detail": detail}
        except Exception as exc:
            anonymous_apex = {"allowed": False, "detail": str(exc)}
        checks.append(
            {
                "name": "anonymous_apex",
                "status": "ok" if anonymous_apex["allowed"] else "warn",
                "detail": anonymous_apex["detail"],
            }
        )
        if not anonymous_apex["allowed"] and status == "ok":
            status = "warn"

    can_execute_convert_lead = bool(org_alias and converted_statuses and anonymous_apex["allowed"])
    notes: List[str] = []
    if not anonymous_apex["allowed"]:
        notes.append(
            "Lead-to-Contact execution requires anonymous Apex permission; same-object merges can still run without it."
        )
    if not converted_statuses:
        notes.append("No converted LeadStatus values were discovered; supply a valid converted status before apply.")
    if duplicate_rule_info["rules"]:
        notes.append(
            "Active Salesforce duplicate rules can block seed/test inserts; allowSave override may be needed during smoke tests."
        )

    return {
        "status": status,
        "org_alias": org_alias or "",
        "identity": identity,
        "api_version": str(identity.get("api_version") or "").strip(),
        "converted_statuses": converted_statuses,
        "lead_statuses": lead_status_rows,
        "duplicate_rules": duplicate_rule_info["rules"],
        "duplicate_rules_error": duplicate_rule_info["error"],
        "can_run_anonymous_apex": anonymous_apex["allowed"],
        "can_execute_convert_lead": can_execute_convert_lead,
        "convert_lead_execution_backend": "anonymous_apex",
        "checks": checks,
        "notes": notes,
    }


def build_dedupe_plan(
    *,
    sources: Sequence[Dict[str, Any]],
    output_path: str,
    review_output_path: str | None = None,
    key_columns: List[str] | None = None,
    threshold: int | None = None,
    local: bool = False,
    survivor_policy: str = "crm_person",
    blocking_keys: List[str] | None = None,
    profile: str = "default",
    block_size_cap: int = 5000,
    limit_per_source: int | None = None,
    overwrite: bool = False,
    include_plan: bool = False,
) -> Dict[str, Any]:
    source_specs = _coerce_sources(sources)
    plan_path = Path(output_path)
    review_path = Path(review_output_path) if review_output_path else None
    plan = dedup_helpers.dedup_enterprise(
        source_specs,
        key_columns=key_columns,
        threshold=threshold,
        output_path=plan_path,
        review_output_path=review_path,
        overwrite=overwrite,
        local=local,
        survivor_policy=survivor_policy,
        blocking_keys=blocking_keys,
        profile=profile,
        block_size_cap=block_size_cap,
        limit_per_source=limit_per_source,
    )
    plan_text = plan_path.read_text(encoding="utf-8")
    result: Dict[str, Any] = {
        "plan_path": str(plan_path),
        "review_output_path": str(review_path) if review_path else "",
        "plan_hash": sfdc_dedupe_apply.plan_digest(plan_text),
        "matching_profile": plan.get("matching_profile") or {},
        "stats": plan.get("stats") or {},
        "warnings": plan.get("warnings") or [],
    }
    if include_plan:
        result["plan"] = plan
    return result


def apply_dedupe_plan(
    *,
    plan_path: str,
    plan_hash: str,
    target: str | None = None,
    converted_status: str | None = None,
    do_not_create_opportunity: bool = True,
    continue_on_error: bool = True,
    state_file: str | None = None,
    resume: bool = False,
    receipt_file: str | None = None,
    max_retries: int = 3,
    retry_base_delay: float = 1.0,
    retry_max_delay: float = 20.0,
    dry_run: bool = False,
) -> Dict[str, Any]:
    if max_retries < 0:
        raise ValueError("max_retries must be >= 0.")
    if retry_base_delay <= 0 or retry_max_delay <= 0:
        raise ValueError("retry delays must be > 0.")
    if retry_base_delay > retry_max_delay:
        raise ValueError("retry_base_delay must be <= retry_max_delay.")
    if resume and not state_file:
        raise ValueError("resume requires state_file.")

    plan_file = Path(plan_path)
    plan_text = plan_file.read_text(encoding="utf-8")
    payload = json.loads(plan_text)
    validated_plan_hash = _validate_plan_hash(plan_text, plan_hash, tool_name="sfdc.dedupe.apply")
    target_org = _resolved_target(target) or ""

    execution_plan = sfdc_dedupe_apply.build_dedupe_operations(
        payload,
        converted_status=converted_status,
        do_not_create_opportunity=do_not_create_opportunity,
    )
    operations = list(execution_plan.get("operations") or [])
    skipped = list(execution_plan.get("skipped") or [])
    skipped_by_reason = dict((execution_plan.get("stats") or {}).get("skipped_by_reason") or {})
    operation_mix = _operation_mix(operations)

    missing_convert_status = [
        entry
        for entry in operations
        if isinstance(entry, dict)
        and _operation_type(entry) == "convert_lead"
        and not str(entry.get("converted_status") or "").strip()
    ]
    if missing_convert_status and not dry_run:
        raise ValueError(
            "converted_status is required to apply convertLead operations from mixed Lead/Contact clusters."
        )

    state_path = Path(state_file) if state_file else None
    receipt_path = Path(receipt_file) if receipt_file else None
    run_started_at = datetime.now(timezone.utc).isoformat()
    state_payload: Dict[str, Any] | None = None
    succeeded_by_index: Dict[int, Dict[str, Any]] = {}
    failed_by_index: Dict[int, Dict[str, Any]] = {}
    start_index = 0

    def _merge_state_index(value: Dict[str, Any]) -> int | None:
        try:
            return int(value.get("index"))
        except Exception:
            return None

    def _state_entries_by_index(entries: Sequence[Dict[str, Any]]) -> Dict[int, Dict[str, Any]]:
        mapped: Dict[int, Dict[str, Any]] = {}
        for entry in entries:
            if not isinstance(entry, dict):
                continue
            entry_index = _merge_state_index(entry)
            if entry_index is None:
                continue
            mapped[entry_index] = dict(entry)
        return mapped

    def _ordered_state_entries(entries_by_index: Dict[int, Dict[str, Any]]) -> List[Dict[str, Any]]:
        return [entries_by_index[index] for index in sorted(entries_by_index)]

    def _persist_state(next_index: int, *, interrupted_run: bool = False) -> None:
        if state_payload is None or state_path is None:
            return
        updated_at = datetime.now(timezone.utc).isoformat()
        state_payload["succeeded"] = _ordered_state_entries(succeeded_by_index)
        state_payload["failed"] = _ordered_state_entries(failed_by_index)
        state_payload["updated_at"] = updated_at
        state_payload["next_index"] = next_index
        if interrupted_run:
            state_payload["interrupted_at"] = updated_at
        else:
            state_payload.pop("interrupted_at", None)
        sfdc_dedupe_apply.write_state(state_path, state_payload)

    if state_path:
        if resume:
            state_payload = sfdc_dedupe_apply.load_state(state_path)
            if str(state_payload.get("plan_digest") or "").strip() not in {"", validated_plan_hash}:
                raise ValueError("State file plan digest does not match the requested plan.")
            state_total_operations = int(state_payload.get("total_operations") or 0)
            if state_total_operations and state_total_operations != len(operations):
                raise ValueError("State file total_operations does not match the plan.")
            state_org_alias = str(state_payload.get("org_alias") or "").strip()
            if state_org_alias and target_org and state_org_alias != target_org:
                raise ValueError("State file org_alias does not match the target org.")
            start_index = max(0, min(int(state_payload.get("next_index") or 0), len(operations)))
            succeeded_by_index = _state_entries_by_index(list(state_payload.get("succeeded") or []))
            failed_by_index = _state_entries_by_index(list(state_payload.get("failed") or []))
        else:
            state_payload = {
                "version": 1,
                "command": "sfdc.dedupe.apply",
                "plan": str(plan_file),
                "plan_digest": validated_plan_hash,
                "org_alias": target_org,
                "total_operations": len(operations),
                "skipped": skipped,
                "continue_on_error": continue_on_error,
                "max_retries": max_retries,
                "retry_base_delay_seconds": retry_base_delay,
                "retry_max_delay_seconds": retry_max_delay,
                "converted_status": str(converted_status or "").strip(),
                "do_not_create_opportunity": bool(do_not_create_opportunity),
                "started_at": run_started_at,
                "updated_at": run_started_at,
                "next_index": 0,
                "succeeded": [],
                "failed": [],
            }
            sfdc_dedupe_apply.write_state(state_path, state_payload)

    if dry_run:
        receipt_payload = sfdc_dedupe_apply.make_receipt(
            command="sfdc.dedupe.apply",
            plan_path=plan_file,
            plan_digest_value=validated_plan_hash,
            org_alias=target_org,
            dry_run=True,
            operations=operations,
            skipped=skipped,
            succeeded=[],
            failed=[],
            resume=resume,
            state_file=state_path,
        )
        if receipt_path is not None:
            sfdc_dedupe_apply.write_receipt(receipt_path, receipt_payload)
        return {
            "plan_hash": validated_plan_hash,
            "org_alias": target_org,
            "dry_run": True,
            "operations_planned": len(operations),
            "operation_mix": operation_mix,
            "skipped": skipped,
            "skipped_by_reason": skipped_by_reason,
            "receipt": receipt_payload,
            "receipt_file": str(receipt_path) if receipt_path else "",
        }

    interrupted = False
    for idx in range(start_index, len(operations)):
        entry = operations[idx]
        if not isinstance(entry, dict):
            failed_by_index[idx] = {"index": idx, "error": "invalid_entry", "retryable": False, "attempts": 0}
            succeeded_by_index.pop(idx, None)
            _persist_state(idx if not continue_on_error else idx + 1)
            if not continue_on_error:
                break
            continue

        operation_type = _operation_type(entry)
        base_entry: Dict[str, Any] = {
            "index": idx,
            "operation_id": entry.get("operation_id"),
            "cluster_id": entry.get("cluster_id"),
            "operation_type": operation_type,
        }
        success_payload: Dict[str, Any] = {}

        if operation_type == "merge":
            object_type = str(entry.get("object_type") or "").strip()
            master_id = str(entry.get("master_id") or "").strip()
            victim_ids = [str(value).strip() for value in (entry.get("victim_ids") or []) if str(value).strip()]
            success_payload = {
                "object_type": object_type,
                "master_id": master_id,
                "victim_ids": victim_ids,
            }
            if not object_type or not master_id or not victim_ids:
                failed_by_index[idx] = {
                    **base_entry,
                    "error": "missing_object_type_master_or_victim_ids",
                    "retryable": False,
                    "attempts": 0,
                }
                succeeded_by_index.pop(idx, None)
                _persist_state(idx if not continue_on_error else idx + 1)
                if not continue_on_error:
                    break
                continue
            execute_operation = partial(
                sfdc_helpers.merge_records,
                object_type,
                master_id,
                victim_ids,
                org_alias=target_org or None,
            )
        elif operation_type == "convert_lead":
            lead_id = str(entry.get("lead_id") or "").strip()
            converted_contact_id = str(entry.get("converted_contact_id") or "").strip()
            converted_account_id = str(entry.get("converted_account_id") or "").strip()
            operation_converted_status = str(entry.get("converted_status") or "").strip()
            operation_do_not_create = bool(entry.get("do_not_create_opportunity", do_not_create_opportunity))
            success_payload = {
                "lead_id": lead_id,
                "converted_contact_id": converted_contact_id,
                "converted_account_id": converted_account_id,
                "converted_status": operation_converted_status,
                "do_not_create_opportunity": operation_do_not_create,
            }
            if not lead_id or not converted_contact_id or not operation_converted_status:
                failed_by_index[idx] = {
                    **base_entry,
                    **success_payload,
                    "error": "missing_lead_id_converted_contact_id_or_converted_status",
                    "retryable": False,
                    "attempts": 0,
                }
                succeeded_by_index.pop(idx, None)
                _persist_state(idx if not continue_on_error else idx + 1)
                if not continue_on_error:
                    break
                continue
            execute_operation = partial(
                sfdc_helpers.convert_lead,
                lead_id,
                converted_contact_id=converted_contact_id,
                converted_account_id=converted_account_id or None,
                converted_status=operation_converted_status,
                do_not_create_opportunity=operation_do_not_create,
                org_alias=target_org or None,
            )
        else:
            succeeded_by_index.pop(idx, None)
            failed_by_index[idx] = {
                **base_entry,
                "error": f"unsupported_operation_type:{operation_type or 'unknown'}",
                "retryable": False,
                "attempts": 0,
            }
            _persist_state(idx if not continue_on_error else idx + 1)
            if not continue_on_error:
                break
            continue

        attempt_num = 0
        operation_succeeded = False
        while True:
            try:
                response = execute_operation()
                succeeded_by_index[idx] = {
                    **base_entry,
                    **success_payload,
                    "response": response,
                    "attempts": attempt_num + 1,
                }
                failed_by_index.pop(idx, None)
                operation_succeeded = True
                break
            except KeyboardInterrupt:
                interrupted = True
                break
            except Exception as exc:
                retryable = sfdc_dedupe_apply.is_retryable_merge_error(exc)
                if retryable and attempt_num < max_retries:
                    attempt_num += 1
                    delay = sfdc_dedupe_apply.retry_delay(attempt_num, retry_base_delay, retry_max_delay)
                    time.sleep(delay)
                    continue
                failed_by_index[idx] = {
                    **base_entry,
                    **success_payload,
                    "error": str(exc),
                    "retryable": retryable,
                    "attempts": attempt_num + 1,
                }
                succeeded_by_index.pop(idx, None)
                break

        if interrupted:
            _persist_state(idx, interrupted_run=True)
        elif operation_succeeded or continue_on_error:
            _persist_state(idx + 1)
        else:
            _persist_state(idx)

        if interrupted:
            break
        if not operation_succeeded and not continue_on_error:
            break

    if interrupted:
        raise RuntimeError("Dedupe apply interrupted. Re-run with resume=True to continue.")

    succeeded = _ordered_state_entries(succeeded_by_index)
    failed = _ordered_state_entries(failed_by_index)
    if state_payload is not None and state_path is not None:
        state_payload["finished_at"] = datetime.now(timezone.utc).isoformat()
        state_payload["completed"] = int(state_payload.get("next_index") or 0) >= len(operations)
        if state_payload["completed"]:
            state_payload["next_index"] = len(operations)
        sfdc_dedupe_apply.write_state(state_path, state_payload)

    receipt_payload = sfdc_dedupe_apply.make_receipt(
        command="sfdc.dedupe.apply",
        plan_path=plan_file,
        plan_digest_value=validated_plan_hash,
        org_alias=target_org,
        dry_run=False,
        operations=operations,
        skipped=skipped,
        succeeded=succeeded,
        failed=failed,
        resume=resume,
        state_file=state_path,
    )
    if receipt_path is not None:
        sfdc_dedupe_apply.write_receipt(receipt_path, receipt_payload)

    return {
        "plan_hash": validated_plan_hash,
        "org_alias": target_org,
        "dry_run": False,
        "operations_planned": len(operations),
        "operation_mix": operation_mix,
        "skipped": skipped,
        "skipped_by_reason": skipped_by_reason,
        "succeeded": succeeded,
        "failed": failed,
        "receipt": receipt_payload,
        "receipt_file": str(receipt_path) if receipt_path else "",
        "state_file": str(state_path) if state_path else "",
        "resume": resume,
        "start_index": start_index,
        "next_index": int((state_payload or {}).get("next_index") or len(operations)),
    }


def verify_dedupe_run(
    *,
    receipt_path: str,
    target: str | None = None,
    plan_path: str | None = None,
) -> Dict[str, Any]:
    receipt_file = Path(receipt_path)
    receipt = _load_json_object(receipt_file, label="dedupe receipt")
    plan_digest_match = None
    resolved_plan_path = ""
    if plan_path:
        plan_file = Path(plan_path)
        plan_payload = _load_json_object(plan_file, label="dedupe plan")
        plan_text = plan_file.read_text(encoding="utf-8")
        resolved_plan_path = str(plan_file)
        plan_digest_match = sfdc_dedupe_apply.plan_digest(plan_text) == str(receipt.get("plan_digest") or "").strip()
        if not isinstance(plan_payload.get("clusters"), list):
            raise ValueError("Dedupe plan must contain a clusters list.")
    target_org = _resolved_target(target or str(receipt.get("org_alias") or "").strip()) or ""
    dry_run = bool(receipt.get("dry_run"))
    succeeded = list(receipt.get("succeeded") or [])
    failed = list(receipt.get("failed") or [])
    skipped = list(receipt.get("skipped") or [])

    verification_results: List[Dict[str, Any]] = []
    if not dry_run:
        if not target_org:
            raise ValueError("A target org is required for live dedupe verification.")
        for entry in succeeded:
            if not isinstance(entry, dict):
                continue
            op_type = _operation_type(entry)
            if op_type == "merge":
                verification_results.append(_verify_merge_operation(entry, org_alias=target_org))
            elif op_type == "convert_lead":
                verification_results.append(_verify_convert_lead_operation(entry, org_alias=target_org))
            else:
                verification_results.append(
                    {
                        "operation_id": entry.get("operation_id"),
                        "operation_type": op_type,
                        "cluster_id": entry.get("cluster_id"),
                        "status": "warn",
                        "expected": {},
                        "actual": {},
                        "mismatches": [f"unsupported_operation_type:{op_type or 'unknown'}"],
                    }
                )

    verified_success = sum(1 for entry in verification_results if entry.get("status") == "success")
    verified_warn = sum(1 for entry in verification_results if entry.get("status") == "warn")
    verified_error = sum(1 for entry in verification_results if entry.get("status") == "error")

    status = "success"
    notes: List[str] = []
    if dry_run:
        status = "warn"
        notes.append("Receipt is a dry-run preview; no live Salesforce verification was performed.")
    elif verified_error:
        status = "partial_success" if verified_success else "error"
    elif failed:
        status = "partial_success" if verified_success else "warn"
    elif verified_warn:
        status = "warn"

    if failed:
        notes.append(f"Receipt contains {len(failed)} failed operation(s); verification only checked succeeded entries.")
    if skipped:
        notes.append(f"Receipt contains {len(skipped)} skipped cluster(s).")
    if plan_path and plan_digest_match is False:
        status = "error"
        notes.append("Provided plan_path does not match the receipt's plan_digest.")

    return {
        "status": status,
        "org_alias": target_org,
        "dry_run": dry_run,
        "receipt_path": str(receipt_file),
        "plan_path": resolved_plan_path,
        "plan_digest": str(receipt.get("plan_digest") or "").strip(),
        "plan_digest_match": plan_digest_match,
        "receipt_summary": {
            "operations_planned": int(receipt.get("operations_planned") or 0),
            "operations_succeeded": int(receipt.get("operations_succeeded") or 0),
            "operations_failed": int(receipt.get("operations_failed") or 0),
            "operations_skipped": int(receipt.get("operations_skipped") or 0),
        },
        "verification_summary": {
            "verified_operations": len(verification_results),
            "verified_success": verified_success,
            "verified_warn": verified_warn,
            "verified_error": verified_error,
        },
        "results": verification_results,
        "failed": failed,
        "skipped": skipped,
        "notes": notes,
        "verified_at": datetime.now(timezone.utc).isoformat(),
    }


__all__ = [
    "apply_dedupe_plan",
    "build_dedupe_plan",
    "dedupe_preflight",
    "verify_dedupe_run",
]
