import React, {
  useState,
  useEffect,
  useRef,
  useCallback,
  useMemo,
} from "react";
import { getErrorMessage } from "@nexuscrm/host-sdk";
import { useNavigate, useParams } from "react-router-dom";
import { useQueryClient } from "@tanstack/react-query";
import { OBJECT_APPROVAL_PROCESS_KEYS } from "./useObjectHasApprovalProcess";
import { useSchemas, useSchema } from "../../infrastructure/api/objectQueries";
import { ArrowLeft, ArrowRight, Save, Loader2 } from "lucide-react";
import { useSuccessToast, useErrorToast } from "@nexuscrm/salesforce-crm-standard-frontend/platform/components/ui";
import { deployMetadata } from "../../infrastructure/api/metadataDeploy";
import { approvalsAPI } from "../../infrastructure/api/approvals";
import { ConfirmationModal } from "@nexuscrm/salesforce-crm-standard-frontend/platform/components/ui";
import type { SearchableSelectOption } from "@nexuscrm/salesforce-crm-standard-frontend/platform/components/ui";
import {
  ApprovalWizardState,
  DEFAULT_NEXT_AUTOMATED_APPROVER,
  generateApprovalProcessXml,
} from "./utils/approvalXmlGenerator";
import type { APIProcessNode } from "./approvalTypes";
import { parseEntryFilter } from "./utils/entryFilterParser";
import {
  parseApprovalProcessConfiguration,
} from "./utils/approvalProcessMetadata";
import {
  ConfigurationStep,
  AutomatedApproverStep,
  ApprovalStepsEditor,
  ReviewAndDeploy,
} from "./components/wizard-steps";
import {
  appendWizardAction,
  deleteWizardAction,
  hydrateApprovalActions,
  mergeStepScopedActions,
  hydrateLegacyStepsFromNodes,
  hydrateMetadataSteps,
  hydrateAllowedSubmitters,
  projectWizardActions,
  updateWizardAction,
} from "./utils/approvalHydration";
import { ProcessActionsSection } from "./components/ProcessActionsSection";
import { validateWizardStep } from "./utils/approvalValidation";
import { tryGetSetupCapabilityPagePath } from "../../core/navigation/setupPagePaths";
import { useApp } from "@nexuscrm/host-sdk";

// ============================================================================
// Main Setup Wizard
// ============================================================================

const DEFAULT_STATE: ApprovalWizardState = {
  fullName: "",
  label: "",
  description: "",
  active: false,
  allowRecall: true,
  showApprovalHistory: true,
  recordEditability: "Admin",
  finalApprovalRecordLock: false,
  finalRejectionRecordLock: false,
  allowedSubmitters: [{ type: "recordCreator" }],
  targetObject: "",
  entryCriteria: { criteriaItems: [] },
  approvalPageFields: ["Name", "CreatedDate", "OwnerId"],
  nextAutomatedApprover: { ...DEFAULT_NEXT_AUTOMATED_APPROVER },
  approvalSteps: [],
  initialSubmissionActions: [],
  stepApprovalActions: [],
  finalApprovalActions: [],
  finalRejectionActions: [],
  recallActions: [],
  reassignActions: [],
};

export function ApprovalProcessSetupWizard() {
  const { processId } = useParams<{ processId?: string }>();
  const navigate = useNavigate();
  const queryClient = useQueryClient();
  const { currentFrontendAppId } = useApp();
  const approvalProcessesPath = tryGetSetupCapabilityPagePath(currentFrontendAppId, "approvalProcesses") ?? "/";
  const { data: schemas = [] } = useSchemas();
  const showSuccess = useSuccessToast();
  const showError = useErrorToast();

  const [state, setState] = useState<ApprovalWizardState>(DEFAULT_STATE);
  const [loading, setLoading] = useState(false);
  const [hydrating, setHydrating] = useState(false);
  const [hydrationFailed, setHydrationFailed] = useState(false);
  const [retryCount, setRetryCount] = useState(0);
  const [stepIndex, setStepIndex] = useState(1);
  const [isDirty, setIsDirty] = useState(false);
  const [validationErrors, setValidationErrors] = useState<
    Record<string, string>
  >({});
  const [showDiscardModal, setShowDiscardModal] = useState(false);
  const initialStateRef = useRef<string>("");
  const pendingNavigationRef = useRef<(() => void) | null>(null);

  // Fetch fields for the selected object (for criteria autocomplete)
  const { data: objectSchema } = useSchema(state.targetObject);
  // Fetch User schema for hierarchy field dropdown (self-referential User lookups)
  const { data: userSchema } = useSchema("User");
  const objectFields = useMemo(
    () =>
      objectSchema?.schema?.fields?.map((f) => ({
        name: f.name,
        label: f.label,
        type: f.type,
      })) ?? [],
    [objectSchema],
  );

  // Derived options for SearchableSelect components
  const schemaOptions: SearchableSelectOption[] = useMemo(
    () =>
      schemas.map((s) => ({ value: s.name, label: s.label, detail: s.name })),
    [schemas],
  );
  const fieldOptions: SearchableSelectOption[] = useMemo(
    () =>
      objectFields.map((f) => ({
        value: f.name,
        label: f.label,
        detail: f.type,
      })),
    [objectFields],
  );
  const schemaFields = useMemo(
    () => objectSchema?.schema?.fields ?? [],
    [objectSchema],
  );
  const userHierarchyFieldOptions: SearchableSelectOption[] = useMemo(
    () =>
      (userSchema?.schema?.fields ?? [])
        .filter(
          (f) =>
            f.type?.toLowerCase() === "reference" &&
            f.referenceTo?.some(
              (ref: string) => ref.toLowerCase() === "user",
            ),
        )
        .map((f) => ({ value: f.name, label: f.label || f.name, detail: f.name })),
    [userSchema],
  );

  const isEditMode = !!processId && processId !== "new";
  const reviewStepIndex = isEditMode ? 5 : 4;

  const wizardActionProjection = useMemo(
    () => projectWizardActions(state, processId ?? "approval-process-draft"),
    [state, processId],
  );
  const wizardActionDisplaySteps = useMemo(
    () =>
      wizardActionProjection.nodes.map((node, index) => {
        const step = state.approvalSteps[index];
        const approvers = (step?.assignedApprover.approver ?? [])
          .map((approver) => approver.displayName || approver.name || approver.id || "")
          .filter(Boolean);
        return {
          key: node.Id,
          index: index + 1,
          name: step?.label || step?.name || node.Name,
          approvers,
          approvalMode:
            (step?.assignedApprover.approver ?? []).length > 1
              ? step?.assignedApprover.whenMultipleApprovers
              : undefined,
          rejectBehaviorLabel:
            step?.rejectBehavior?.type === "BackToPrevious"
              ? "Back to previous step"
              : "Reject request",
          description: step?.description || "—",
        };
      }),
    [state.approvalSteps, wizardActionProjection.nodes],
  );

  const replaceWizardState = (nextState: ApprovalWizardState) => {
    setState(nextState);
    setIsDirty(true);
  };

  // Form Binding Helper
  const updateState = (update: Partial<ApprovalWizardState>) => {
    setState((prev) => ({ ...prev, ...update }));
    setIsDirty(true);
  };

  // Derived Label -> DeveloperName auto-fill (create mode only)
  // Uses setState directly to avoid marking the form dirty on mount
  useEffect(() => {
    if (!isEditMode && state.label && !state.fullName) {
      setState((prev) => ({
        ...prev,
        fullName: prev.label.replace(/[^a-zA-Z0-9]/g, "_"),
      }));
    }
  }, [state.label, isEditMode, state.fullName]);

  // Hydrate state from existing process when editing
  useEffect(() => {
    if (!isEditMode) return;
    let cancelled = false;
    const loadExisting = async () => {
      setHydrating(true);
      setHydrationFailed(false);
      try {
        const [proc, nodes, actions] = await Promise.all([
          approvalsAPI.getApprovalProcessDetail(processId!),
          approvalsAPI.getProcessNodes(processId!),
          approvalsAPI.getProcessActions(processId!),
        ]);
        if (cancelled) return;
        if (!proc) {
          showError("Approval process not found.");
          setHydrationFailed(true);
          return;
        }

        const parsedMetadata = parseApprovalProcessConfiguration(proc.Metadata);
        const metadataSteps = parsedMetadata.approvalSteps;
        const metadataAllowedSubmitters = parsedMetadata.allowedSubmitters;
        const metadataUserRefs = metadataSteps.flatMap((step) =>
          (step.assignedApprover.approver ?? [])
            .filter((approver) => approver.type === "user")
            .map((approver) => approver.id ?? approver.name)
            .filter((value): value is string => Boolean(value)),
        );
        const allowedSubmitterUserRefs = metadataAllowedSubmitters
          .filter((submitter) => submitter.type === "user")
          .map((submitter) => submitter.id ?? submitter.submitter)
          .filter((value): value is string => Boolean(value));
        const allowedSubmitterRoleRefs = metadataAllowedSubmitters
          .filter(
            (submitter) =>
              submitter.type === "role" ||
              submitter.type === "roleAndSubordinates",
          )
          .map((submitter) => submitter.id ?? submitter.submitter)
          .filter((value): value is string => Boolean(value));

        // Batch-resolve approver usernames and IDs to display names
        const userRefsToResolve = [
          ...new Set([
            ...nodes
              .filter((n: APIProcessNode) => !n.ApproverUser?.Name && n.ApproverUserId)
              .map((n: APIProcessNode) => n.ApproverUserId!),
            ...metadataUserRefs,
            ...allowedSubmitterUserRefs,
          ]),
        ];
        const usernameToInfo = new Map<string, { name: string; id: string }>();
        if (userRefsToResolve.length > 0) {
          try {
            const byUsername = await approvalsAPI.getUsersByUsernames(userRefsToResolve);
            for (const u of byUsername) {
              const displayName =
                [u.FirstName, u.LastName].filter(Boolean).join(" ") ||
                u.Username ||
                u.Id;
              usernameToInfo.set(u.Username, { name: displayName, id: u.Id });
            }
          } catch {
            /* best-effort */
          }
        }
        if (userRefsToResolve.length > 0) {
          try {
            const byId = await approvalsAPI.getUsersById(userRefsToResolve);
            for (const u of byId) {
              const displayName =
                [u.FirstName, u.LastName].filter(Boolean).join(" ") ||
                u.Username ||
                u.Id;
              usernameToInfo.set(u.Id, { name: displayName, id: u.Id });
              if (u.Username) {
                usernameToInfo.set(u.Username, { name: displayName, id: u.Id });
              }
            }
          } catch {
            /* best-effort */
          }
        }
        for (const node of nodes) {
          const displayName =
            node.ApproverUser?.FirstName || node.ApproverUser?.LastName
              ? [node.ApproverUser.FirstName, node.ApproverUser.LastName]
                  .filter(Boolean)
                  .join(" ")
              : node.ApproverUser?.Name;
          if (node.ApproverUserId && displayName) {
            usernameToInfo.set(node.ApproverUserId, {
              name: displayName,
              id: node.ApproverUserId,
            });
          }
        }

        const roleRefToInfo = new Map<string, { name: string; id: string }>();
        if (allowedSubmitterRoleRefs.length > 0) {
          try {
            const byName = await approvalsAPI.getUserRolesByNames(allowedSubmitterRoleRefs);
            for (const role of byName) {
              roleRefToInfo.set(role.Name, { name: role.Name, id: role.Id });
            }
          } catch {
            /* best-effort */
          }
        }
        if (allowedSubmitterRoleRefs.length > 0) {
          try {
            const byId = await approvalsAPI.getUserRolesById(allowedSubmitterRoleRefs);
            for (const role of byId) {
              roleRefToInfo.set(role.Id, { name: role.Name, id: role.Id });
              roleRefToInfo.set(role.Name, { name: role.Name, id: role.Id });
            }
          } catch {
            /* best-effort */
          }
        }

        const legacyHydratedSteps = hydrateLegacyStepsFromNodes(nodes, usernameToInfo);

        const hydratedSteps =
          metadataSteps.length > 0
            ? hydrateMetadataSteps(metadataSteps, usernameToInfo)
            : legacyHydratedSteps;
        const hydratedStepsWithActions = mergeStepScopedActions(
          hydratedSteps,
          nodes,
          actions,
        );
        const hydratedAllowedSubmitters = hydrateAllowedSubmitters(
          metadataAllowedSubmitters,
          parsedMetadata.hasExplicitAllowedSubmitters,
          usernameToInfo,
          roleRefToInfo,
        );

        const hydratedCriteria = parseEntryFilter(proc.EntryFilter);
        const hydratedActions = hydrateApprovalActions(actions);
        const derivedNextAutomatedApprover =
          parsedMetadata.nextAutomatedApprover ??
          (hydratedStepsWithActions.some((step) =>
            (step.assignedApprover.approver ?? []).some(
              (approver) => approver.type === "hierarchy",
            ),
          )
            ? { ...DEFAULT_NEXT_AUTOMATED_APPROVER }
            : { ...DEFAULT_NEXT_AUTOMATED_APPROVER });

        setState({
          fullName: proc.DeveloperName ?? "",
          label: proc.Name,
          description: proc.Description ?? "",
          active: proc.State === "Active",
          allowRecall: proc.AllowRecall ?? true,
          showApprovalHistory: true,
          recordEditability: (proc.LockType === "WorkItem"
            ? "WorkItem"
            : "Admin") as ApprovalWizardState["recordEditability"],
          finalApprovalRecordLock: proc.FinalApprovalRecordLock ?? false,
          finalRejectionRecordLock: proc.FinalRejectionRecordLock ?? false,
          allowedSubmitters: hydratedAllowedSubmitters,
          targetObject: proc.TableEnumOrId,
          entryCriteria: hydratedCriteria,
          approvalPageFields: ["Name", "CreatedDate", "OwnerId"],
          nextAutomatedApprover: derivedNextAutomatedApprover,
          approvalSteps: hydratedStepsWithActions,
          ...hydratedActions,
        });
      } catch (err: unknown) {
        showError(`Failed to load process: ${getErrorMessage(err)}`);
        if (!cancelled) setHydrationFailed(true);
      } finally {
        if (!cancelled) setHydrating(false);
      }
    };
    loadExisting();
    return () => {
      cancelled = true;
    };
  }, [processId, isEditMode, showError, retryCount]);

  // Track initial state for dirty detection after hydration
  useEffect(() => {
    if (!hydrating && !hydrationFailed) {
      initialStateRef.current = JSON.stringify(state);
      setIsDirty(false);
    }
  }, [hydrating, hydrationFailed]); // eslint-disable-line react-hooks/exhaustive-deps

  // Unsaved changes guard: browser close/refresh
  useEffect(() => {
    if (!isDirty) return;
    const handler = (e: BeforeUnloadEvent) => {
      e.preventDefault();
    };
    window.addEventListener("beforeunload", handler);
    return () => window.removeEventListener("beforeunload", handler);
  }, [isDirty]);

  // Navigate with dirty guard: shows modal if unsaved changes exist
  const guardedNavigate = useCallback(
    (navigateFn: () => void) => {
      if (isDirty) {
        pendingNavigationRef.current = navigateFn;
        setShowDiscardModal(true);
      } else {
        navigateFn();
      }
    },
    [isDirty],
  );

  // Returns error map for a step without setting state
  const validateStepErrors = useCallback(
    (step: number): Record<string, string> => {
      return validateWizardStep(step, state);
    },
    [state],
  );

  // Validate step before navigation — sets errors and returns pass/fail
  const validateStep = useCallback(
    (step: number): boolean => {
      const errors = validateStepErrors(step);
      setValidationErrors(errors);
      return Object.keys(errors).length === 0;
    },
    [validateStepErrors],
  );

  // ========================================================================
  // Deployment / Saving
  // ========================================================================

  const handleSaveAndDeploy = async () => {
    const step1Errors = validateStepErrors(1);
    const step2Errors = validateStepErrors(2);
    const step3Errors = validateStepErrors(3);
    const allErrors = { ...step1Errors, ...step2Errors, ...step3Errors };
    setValidationErrors(allErrors);
    if (Object.keys(allErrors).length > 0) {
      showError("Please fix validation errors before deploying.");
      return;
    }

    setLoading(true);
    try {
      const xmlPayload = generateApprovalProcessXml(state);

      const files = {
        [`approvalProcesses/${state.targetObject}.${state.fullName}.approvalProcess`]:
          xmlPayload,
      };

      await deployMetadata(
        files,
        [`${state.targetObject}.${state.fullName}`],
        "ApprovalProcess",
      );

      showSuccess("Approval Process Deployed Successfully!");
      setIsDirty(false);
      navigate(approvalProcessesPath);
    } catch (e: unknown) {
      console.error(e);
      showError((e as Error).message || "Deploy failed");
    } finally {
      setLoading(false);
      // Deploy can create the process as Active or flip Active on edit:
      // re-derive the object-level "has an active process" gates.
      queryClient.invalidateQueries({ queryKey: OBJECT_APPROVAL_PROCESS_KEYS.root });
    }
  };

  // ========================================================================
  // Step indicator
  // ========================================================================

  const renderStepNumbers = () => (
    <div className="flex items-center gap-2 mb-8">
      {Array.from({ length: reviewStepIndex }, (_, index) => index + 1).map((num) => (
        <React.Fragment key={num}>
          <button
            onClick={() => setStepIndex(num)}
            className={`w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-colors ${stepIndex === num ? "bg-blue-600 text-white" : "bg-gray-200 text-gray-600 dark:bg-gray-800 dark:text-gray-400 hover:bg-gray-300"}`}
          >
            {num}
          </button>
          {num < reviewStepIndex && <div className="w-8 h-1 bg-gray-200 dark:bg-gray-800" />}
        </React.Fragment>
      ))}
    </div>
  );

  // Shared step props
  const stepProps = {
    state,
    updateState,
    validationErrors,
    setValidationErrors,
    isEditMode,
  };

  return (
    <div className="max-w-4xl mx-auto space-y-6">
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-2xl font-bold text-gray-900 dark:text-white">
            {isEditMode
              ? "Edit Approval Process"
              : "New Approval Process Setup"}
          </h1>
          <button
            onClick={() => guardedNavigate(() => navigate(-1))}
            className="inline-flex items-center gap-1 text-sm text-blue-600 hover:underline mt-1"
          >
            <ArrowLeft className="w-4 h-4" /> Back
          </button>
        </div>
        <button
          onClick={handleSaveAndDeploy}
          disabled={loading || hydrating || hydrationFailed}
          className="inline-flex items-center gap-2 px-5 py-2.5 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50"
        >
          <Save className="w-4 h-4" />
          {loading
            ? "Deploying..."
            : isEditMode
              ? "Save Changes"
              : "Save & Deploy"}
        </button>
      </div>

      {renderStepNumbers()}

      {hydrating ? (
        <div className="flex items-center justify-center py-24">
          <Loader2 className="w-6 h-6 animate-spin text-blue-600" />
          <span className="ml-3 text-gray-500 dark:text-gray-400">
            Loading approval process…
          </span>
        </div>
      ) : hydrationFailed ? (
        <div className="text-center py-24">
          <p className="text-red-600 dark:text-red-400 font-medium mb-4">
            Failed to load approval process.
          </p>
          <div className="flex items-center justify-center gap-3">
            <button
              onClick={() => navigate(approvalProcessesPath)}
              className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50"
            >
              Back to List
            </button>
            <button
              onClick={() => setRetryCount((c) => c + 1)}
              className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700"
            >
              Retry
            </button>
          </div>
        </div>
      ) : (
        <div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm p-6">
          {stepIndex === 1 && (
            <ConfigurationStep
              {...stepProps}
              schemaOptions={schemaOptions}
              fieldOptions={fieldOptions}
              schemaFields={schemaFields}
              validateStep={validateStep}
              setStepIndex={setStepIndex}
            />
          )}
          {stepIndex === 2 && (
            <AutomatedApproverStep
              {...stepProps}
              userHierarchyFieldOptions={userHierarchyFieldOptions}
              validateStep={validateStep}
              setStepIndex={setStepIndex}
            />
          )}
          {stepIndex === 3 && (
            <ApprovalStepsEditor
              {...stepProps}
              fieldOptions={fieldOptions}
              schemaFields={schemaFields}
              validateStep={validateStep}
              setStepIndex={setStepIndex}
            />
          )}
          {isEditMode && stepIndex === 4 && (
            <div className="space-y-6">
              <div>
                <h2 className="text-lg font-bold text-gray-900 dark:text-white">
                  Step 4: Configure Actions
                </h2>
                <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                  Action changes are staged with the rest of this approval process and persist only when Save Changes deploys its metadata.
                </p>
              </div>
              <ProcessActionsSection
                mode="editable"
                processDefinitionId={processId!}
                actions={wizardActionProjection.actions}
                steps={wizardActionProjection.nodes}
                displaySteps={wizardActionDisplaySteps}
                fieldOptions={objectFields}
                schemaFields={schemaFields}
                finalApprovalRecordLock={state.finalApprovalRecordLock ?? false}
                finalRejectionRecordLock={state.finalRejectionRecordLock ?? false}
                onActionsChanged={() => undefined}
                onCreateAction={async (action) => {
                  replaceWizardState(appendWizardAction(state, wizardActionProjection.nodes, action));
                }}
                onUpdateAction={async (action, patch) => {
                  replaceWizardState(updateWizardAction(state, wizardActionProjection.nodes, action, patch));
                }}
                onDeleteAction={async (action) => {
                  replaceWizardState(deleteWizardAction(state, wizardActionProjection.nodes, action));
                }}
                onUpdateLock={async (field, value) => {
                  if (field === "FinalApprovalRecordLock") {
                    updateState({ finalApprovalRecordLock: value });
                  } else {
                    updateState({ finalRejectionRecordLock: value });
                  }
                }}
              />
              <div className="flex justify-between">
                <button
                  onClick={() => setStepIndex(3)}
                  className="inline-flex items-center gap-1 px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md"
                >
                  <ArrowLeft className="w-4 h-4" /> Previous
                </button>
                <button
                  onClick={() => setStepIndex(5)}
                  className="inline-flex items-center gap-1 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
                >
                  Review & Deploy <ArrowRight className="w-4 h-4" />
                </button>
              </div>
            </div>
          )}
          {stepIndex === reviewStepIndex && (
            <ReviewAndDeploy
              {...stepProps}
              schemas={schemas}
              handleSaveAndDeploy={handleSaveAndDeploy}
              loading={loading}
              hydrating={hydrating}
              hydrationFailed={hydrationFailed}
              setStepIndex={setStepIndex}
            />
          )}
        </div>
      )}

      {/* Unsaved Changes Modal */}
      <ConfirmationModal
        isOpen={showDiscardModal}
        onClose={() => {
          setShowDiscardModal(false);
          pendingNavigationRef.current = null;
        }}
        onConfirm={() => {
          setShowDiscardModal(false);
          setIsDirty(false);
          const nav = pendingNavigationRef.current;
          pendingNavigationRef.current = null;
          if (nav) nav();
        }}
        title="Unsaved Changes"
        message="You have unsaved changes. Are you sure you want to leave? Your changes will be lost."
        confirmLabel="Discard Changes"
        variant="warning"
      />
    </div>
  );
}

export default ApprovalProcessSetupWizard;
