import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import toast from "react-hot-toast";

import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
import { api } from "@/services/api";
import {
  ruleEngineService,
  type RuleEngineListParams,
  type SubmitPriorityRuleChangePayload,
  type SubmitRateChangePayload,
} from "@/services/ruleEngine/ruleEngine.service";
import {
  LEGACY_APPROVAL_ROLES,
  RULE_ENGINE_SELECT_NONE,
} from "@/pages/ruleEngine/config/resources";
import type {
  RuleEngineRecord,
  RuleEngineResourceSlug,
} from "@/types/rule-engine";
import {
  invalidateRuleEngineList,
  patchRuleEngineListRecord,
} from "@/utils/queryInvalidation";
import { extractErrorMessage } from "@/utils/errorExtractor";

export const useRuleEngineList = (
  resource: RuleEngineResourceSlug,
  params: RuleEngineListParams,
) =>
  useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.list(resource, params),
    queryFn: () => ruleEngineService.list(resource, params),
  });

/** Full (page-walked) list used by the reorder dialog and create-position picker. */
export const useRuleEngineOrderList = (
  resource: RuleEngineResourceSlug,
  enabled: boolean,
  sortBy?: string,
) =>
  useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.orderList(resource),
    queryFn: () =>
      ruleEngineService.listAll(resource, {
        sortBy,
        sortOrder: "ASC",
      }),
    enabled,
  });

export const useRuleEngineOrderMutations = (resource: RuleEngineResourceSlug) => {
  const qc = useQueryClient();

  const reorder = useMutation({
    mutationFn: (payload: { ids: string[]; requiresDirectorApproval?: boolean }) =>
      ruleEngineService.reorder(resource, payload),
    onSuccess: async () => {
      toast.success("Order updated");
      await invalidateRuleEngineList(qc, resource);
      await qc.invalidateQueries({ queryKey: QUERY_KEYS.RULE_ENGINE.orderList(resource) });
    },
    onError: (err) => toast.error(extractErrorMessage(err, "Failed to update order")),
  });

  const moveOrder = useMutation({
    mutationFn: ({ id, direction }: { id: string; direction: "up" | "down" }) =>
      ruleEngineService.moveOrder(resource, id, direction),
    onSuccess: async () => {
      await invalidateRuleEngineList(qc, resource);
      await qc.invalidateQueries({ queryKey: QUERY_KEYS.RULE_ENGINE.orderList(resource) });
    },
    onError: () => toast.error("Cannot move item further in that direction"),
  });

  return { reorder, moveOrder };
};

export const useCargoTypeParentOptions = (excludeId?: string, enabled = true) =>
  useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.selectOptions("cargo-types"),
    queryFn: () => ruleEngineService.listAll<RuleEngineRecord>("cargo-types"),
    enabled,
    select: (rows) => {
      const noneOption = { label: "None", value: RULE_ENGINE_SELECT_NONE };
      const parents = rows
        .filter((row) => row.id && String(row.id) !== excludeId)
        .map((row) => {
          const name = String(row.cargoTypeName ?? "").trim();
          const code = String(row.code ?? "").trim();
          const label =
            name && code ? `${name} (${code})` : name || code || String(row.id);
          return { label, value: String(row.id) };
        });
      return [noneOption, ...parents];
    },
  });

/**
 * Cargo-type options restricted to LEAF nodes (actual commodities, not parent
 * groups). A node is a leaf when no other cargo type names it as parent. Used
 * by the Rate form's "Bulk cargo type" picker.
 */
export const useCargoLeafOptions = (enabled = true) =>
  useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.selectOptions("cargo-types", { leafOnly: true }),
    queryFn: () => ruleEngineService.listAll<RuleEngineRecord>("cargo-types"),
    enabled,
    select: (rows) => {
      const parentIds = new Set(
        rows
          .map((row) => row.parentGroupId)
          .filter((id): id is string => Boolean(id))
          .map((id) => String(id)),
      );
      return rows
        .filter((row) => row.id && !parentIds.has(String(row.id)))
        .map((row) => {
          const name = String(row.cargoTypeName ?? "").trim();
          const code = String(row.code ?? "").trim();
          const label =
            name && code ? `${name} (${code})` : name || code || String(row.id);
          // The commodity's unit of measure rides along so the rate form can
          // offer per-item units for counted (break-bulk) commodities.
          return {
            label,
            value: String(row.id),
            unitOfMeasure: String(row.unitOfMeasure ?? ""),
          };
        });
    },
  });

export function buildContainerTypeSelectOptions(
  rows: RuleEngineRecord[],
  includeNone: boolean,
): { label: string; value: string }[] {
  const options = rows
    .filter((row) => row.id)
    .map((row) => {
      const label = String(row.label ?? "").trim();
      const code = String(row.code ?? "").trim();
      const size = row.sizeFt ? `${String(row.sizeFt)}ft` : "";
      const parts = [label || code || String(row.id), size].filter(Boolean);
      return {
        label: parts.join(" - "),
        value: String(row.id),
      };
    });

  if (!includeNone) return options;
  return [{ label: "None", value: RULE_ENGINE_SELECT_NONE }, ...options];
}

export const useContainerTypeOptions = (
  includeNone = true,
  enabled = true,
) =>
  useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.selectOptions('container-types', {
      includeNone,
    }),
    queryFn: () =>
      ruleEngineService.listAll<RuleEngineRecord>("container-types"),
    enabled,
    select: (rows) => buildContainerTypeSelectOptions(rows, includeNone),
  });

/**
 * Approval-step role options, sourced from the live IAM position types. The
 * three pre-IAM role strings are appended (marked "(legacy)") so an approval
 * rule still stored against one of them renders its label instead of an empty
 * select; a position type that reuses one of those values wins the dedupe.
 */
export const useApprovalRoleOptions = (enabled = true) =>
  useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.selectOptions("approval-rules", {
      positionTypes: true,
    }),
    queryFn: () => ruleEngineService.getApprovalPositionTypes(),
    enabled,
    select: (rows): { label: string; value: string }[] => {
      const byValue = new Map<string, { label: string; value: string }>();
      for (const row of rows) {
        const value = String(row?.value ?? "").trim();
        if (!value) continue;
        byValue.set(value, { label: String(row.label ?? "").trim() || value, value });
      }
      for (const legacy of LEGACY_APPROVAL_ROLES) {
        if (!byValue.has(legacy.value)) byValue.set(legacy.value, legacy);
      }
      return [...byValue.values()];
    },
  });

/** A yard option that remembers its country, so callers can filter by leg. */
export interface YardOption {
  label: string;
  value: string;
  country: string;
}

/**
 * Active yards for the rate form's origin/destination pickers. The country
 * rides along on each option because which yards are legal depends on the
 * rate's direction (import starts in Djibouti, export starts in Ethiopia).
 */
export const useYardOptions = (enabled = true) =>
  useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.selectOptions("yards", { activeOnly: true }),
    queryFn: () => ruleEngineService.listAll<RuleEngineRecord>("yards"),
    enabled,
    select: (rows): YardOption[] =>
      rows
        .filter((row) => row.id && row.isActive !== false)
        .map((row) => {
          const label = String(row.label ?? "").trim();
          const code = String(row.code ?? "").trim();
          return {
            label: label && code ? `${label} (${code})` : label || code || String(row.id),
            value: String(row.id),
            country: String(row.country ?? ""),
          };
        }),
  });

/**
 * Active wagon-type options for the cargo-type / container-type "Wagon type"
 * picker. The FK the selection sets drives train-scheduling wagon resolution.
 */
export const useWagonTypeOptions = (enabled = true) =>
  useQuery({
    ...api.wagonTypes.list.queryOptions(),
    enabled,
    select: (rows: { id: string; code: string; name: string; isActive?: boolean }[]) =>
      rows
        .filter((wt) => wt.isActive !== false)
        .map((wt) => ({
          label: wt.name ? `${wt.name} (${wt.code})` : wt.code,
          value: wt.id,
        })),
  });

export const useLiveRateOptions = (enabled = true) =>
  useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.selectOptions("rates", { status: "LIVE" }),
    queryFn: () =>
      ruleEngineService.listAll<RuleEngineRecord>("rates", { status: "LIVE" }),
    enabled,
    select: (rows) =>
      rows
        .filter((row) => row.id)
        .map((row) => {
          const rateType = String(row.rateType ?? "").replace(/_/g, " ");
          const currency = String(row.currency ?? "");
          const value = row.rateValue != null ? String(row.rateValue) : "";
          const unit = row.rateUnit ? String(row.rateUnit).replace(/_/g, " ") : "";
          const parts = [rateType, currency, value, unit].filter(Boolean);
          return {
            label: parts.join(" · "),
            value: String(row.id),
          };
        }),
  });

export const useApprovalChain = (enabled: boolean) =>
  useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.chain,
    queryFn: () => ruleEngineService.getApprovalChain(),
    enabled,
  });

export const useRuleEngineMutations = (resource: RuleEngineResourceSlug) => {
  const qc = useQueryClient();

  const create = useMutation({
    mutationFn: (payload: Record<string, unknown>) =>
      api.ruleEngine.create.call({ resource, payload }),
    onSuccess: async (created) => {
      toast.success("Created successfully");
      patchRuleEngineListRecord(qc, resource, created);
      await invalidateRuleEngineList(qc, resource);
    },
    onError: (err) =>
      toast.error(extractErrorMessage(err, "Failed to create record")),
  });

  const update = useMutation({
    mutationFn: ({
      id,
      payload,
    }: {
      id: string;
      payload: Record<string, unknown>;
    }) => api.ruleEngine.update.call({ resource, id, payload }),
    onSuccess: async (updated) => {
      toast.success("Updated successfully");
      patchRuleEngineListRecord(qc, resource, updated);
      await invalidateRuleEngineList(qc, resource);
    },
    onError: (err) =>
      toast.error(extractErrorMessage(err, "Failed to update record")),
  });

  const remove = useMutation({
    mutationFn: (id: string) =>
      api.ruleEngine.remove.call({ resource, id }),
    onSuccess: async () => {
      toast.success("Deleted successfully");
      await invalidateRuleEngineList(qc, resource);
    },
    onError: (err) =>
      toast.error(extractErrorMessage(err, "Failed to delete record")),
  });

  return { create, update, remove };
};

/**
 * Priority-rule approval workflow. Every create/update/delete of a priority
 * config is SUBMITTED as a change request; an approver applies or rejects it.
 * Backend messages (range collision, gap, ceiling) surface verbatim — through
 * `onErrorMessage` (the page shows them in a modal) or a toast as fallback.
 */
export const usePriorityRuleWorkflow = (
  enabled: boolean,
  onErrorMessage?: (message: string) => void,
) => {
  const qc = useQueryClient();

  const backendMessage = (err: unknown, fallback: string) => {
    const msg = (err as { response?: { data?: { message?: string | string[] } } })
      ?.response?.data?.message;
    if (Array.isArray(msg)) return msg.join(", ");
    return msg || fallback;
  };

  const showError = (err: unknown, fallback: string) => {
    const message = backendMessage(err, fallback);
    if (onErrorMessage) onErrorMessage(message);
    else toast.error(message);
  };

  const pending = useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.priorityRuleChanges,
    queryFn: () => ruleEngineService.listPriorityRuleChanges("PENDING"),
    enabled,
  });

  const invalidate = async () => {
    await qc.invalidateQueries({
      queryKey: QUERY_KEYS.RULE_ENGINE.priorityRuleChanges,
    });
    await invalidateRuleEngineList(qc, "priority-configs");
    // The full order-list backs the auto-filled min field — keep it fresh too.
    await qc.invalidateQueries({
      queryKey: QUERY_KEYS.RULE_ENGINE.orderList("priority-configs"),
    });
  };

  const submit = useMutation({
    mutationFn: (payload: SubmitPriorityRuleChangePayload) =>
      ruleEngineService.submitPriorityRuleChange(payload),
    onSuccess: async () => {
      toast.success("Change submitted for approval — the team has been notified");
      await invalidate();
    },
    onError: (err) => showError(err, "Failed to submit change"),
  });

  const approve = useMutation({
    mutationFn: ({ id, decisionNote }: { id: string; decisionNote?: string }) =>
      ruleEngineService.approvePriorityRuleChange(id, decisionNote),
    onSuccess: async () => {
      toast.success("Change approved and applied");
      await invalidate();
    },
    onError: (err) => showError(err, "Failed to approve change"),
  });

  const reject = useMutation({
    mutationFn: ({ id, decisionNote }: { id: string; decisionNote?: string }) =>
      ruleEngineService.rejectPriorityRuleChange(id, decisionNote),
    onSuccess: async () => {
      toast.success("Change rejected");
      await invalidate();
    },
    onError: (err) => showError(err, "Failed to reject change"),
  });

  return { pending, submit, approve, reject };
};

/**
 * Approval workflow for edits to LIVE rates. The live rate keeps its current
 * value until a change is approved, so the rates list is invalidated on every
 * outcome — including reject, which restores the row's "no pending" state.
 */
export const useRateChangeWorkflow = (
  enabled: boolean,
  onErrorMessage?: (message: string) => void,
) => {
  const qc = useQueryClient();

  const showError = (err: unknown, fallback: string) => {
    const raw = (err as { response?: { data?: { message?: string | string[] } } })
      ?.response?.data?.message;
    const message = (Array.isArray(raw) ? raw.join(", ") : raw) || fallback;
    if (onErrorMessage) onErrorMessage(message);
    else toast.error(message);
  };

  const pending = useQuery({
    queryKey: QUERY_KEYS.RULE_ENGINE.rateChanges,
    queryFn: () => ruleEngineService.listRateChanges("PENDING"),
    enabled,
  });

  const invalidate = async () => {
    await qc.invalidateQueries({ queryKey: QUERY_KEYS.RULE_ENGINE.rateChanges });
    await invalidateRuleEngineList(qc, "rates");
  };

  const submit = useMutation({
    mutationFn: (payload: SubmitRateChangePayload) =>
      ruleEngineService.submitRateChange(payload),
    onSuccess: async () => {
      toast.success(
        "Change submitted for approval — the rate keeps its current value until approved",
      );
      await invalidate();
    },
    onError: (err) => showError(err, "Failed to submit rate change"),
  });

  const approve = useMutation({
    mutationFn: ({ id, decisionNote }: { id: string; decisionNote?: string }) =>
      ruleEngineService.approveRateChange(id, decisionNote),
    onSuccess: async () => {
      toast.success("Rate change approved — the new rate is now live");
      await invalidate();
    },
    onError: (err) => showError(err, "Failed to approve rate change"),
  });

  const reject = useMutation({
    mutationFn: ({ id, decisionNote }: { id: string; decisionNote?: string }) =>
      ruleEngineService.rejectRateChange(id, decisionNote),
    onSuccess: async () => {
      toast.success("Rate change rejected — the rate keeps its current value");
      await invalidate();
    },
    onError: (err) => showError(err, "Failed to reject rate change"),
  });

  return { pending, submit, approve, reject };
};

export const useRateWorkflow = () => {
  const qc = useQueryClient();

  const submit = useMutation({
    mutationFn: (id: string) => api.ruleEngine.submitRate.call({ id }),
    onSuccess: async (updated) => {
      toast.success("Rate submitted for approval");
      patchRuleEngineListRecord(qc, "rates", updated);
      await invalidateRuleEngineList(qc, "rates");
    },
    onError: (err) => toast.error(extractErrorMessage(err, "Failed to submit rate")),
  });

  const approve = useMutation({
    mutationFn: (id: string) => api.ruleEngine.approveRate.call({ id }),
    onSuccess: async (updated) => {
      toast.success("Rate approved");
      patchRuleEngineListRecord(qc, "rates", updated);
      await invalidateRuleEngineList(qc, "rates");
    },
    onError: (err) => toast.error(extractErrorMessage(err, "Failed to approve rate")),
  });

  return { submit, approve };
};
