import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import toast from "react-hot-toast";
import type { QueryClient } from "@tanstack/react-query";
import type { Freight } from "@edr/types";

import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
import {
  contractsService,
  type ContractListFilter,
  type SignContractPayload,
} from "@/services/contracts.service";
import { extractErrorMessage } from "@/utils/errorExtractor";

function invalidateContractDetail(qc: QueryClient, id: string): Promise<void> {
  return Promise.all([
    qc.invalidateQueries({ queryKey: QUERY_KEYS.CONTRACTS.byId(id) }),
    qc.invalidateQueries({ queryKey: QUERY_KEYS.CONTRACTS.ROOT }),
  ]).then(() => undefined);
}

export function useContractList(filter?: ContractListFilter, enabled = true) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.list(filter),
    queryFn: () => contractsService.list(filter),
    enabled,
  });
}

export function useContractListSummary(
  filter?: ContractListFilter,
  enabled = true,
) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.listSummary(filter),
    queryFn: () => contractsService.getListSummary(filter),
    enabled,
  });
}

export function useContractDetail(id: string | undefined) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.byId(id ?? ""),
    queryFn: () => contractsService.getById(id!),
    enabled: Boolean(id),
  });
}

export function useContractClearanceQueue(enabled = true) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.clearanceQueue("GL"),
    queryFn: () => contractsService.getClearanceQueue(),
    enabled,
  });
}

export function useEtClearanceQueue(enabled = true) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.clearanceQueue("ET"),
    queryFn: () => contractsService.getEtClearanceQueue(),
    enabled,
  });
}

export function useDjClearanceQueue(enabled = true) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.clearanceQueue("DJ"),
    queryFn: () => contractsService.getDjClearanceQueue(),
    enabled,
  });
}

/** Path A self-clearance queue (Operations reviews non-customs contracts). */
export function useOpsClearanceQueue(enabled = true) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.clearanceQueue("OPS"),
    queryFn: () => contractsService.getOpsClearanceQueue(),
    enabled,
  });
}

export function useContractClearanceHistory(enabled = true) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.clearanceHistory("GL"),
    queryFn: () => contractsService.getClearanceHistory(),
    enabled,
  });
}

export function useOpsClearanceHistory(enabled = true) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.clearanceHistory("OPS"),
    queryFn: () => contractsService.getOpsClearanceHistory(),
    enabled,
  });
}

export function useContractMilestones(id: string | undefined) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.milestones(id ?? ""),
    queryFn: () => contractsService.listMilestonesForContract(id!),
    enabled: Boolean(id),
  });
}

export function useContractCapacity(id: string | undefined) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.capacity(id ?? ""),
    queryFn: () => contractsService.getCapacity(id!),
    enabled: Boolean(id),
  });
}

export function useBookingMilestones(bookingId: string | undefined) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.bookingMilestones(bookingId ?? ""),
    queryFn: () => contractsService.listMilestonesForBooking(bookingId!),
    enabled: Boolean(bookingId),
  });
}

export function useContractMutations(contractId: string) {
  const qc = useQueryClient();
  const onSuccess = (data: { id: string }, message: string) => {
    toast.success(message);
    void invalidateContractDetail(qc, data.id);
    // Approving or editing can change who holds document-editing rights (it
    // passes to the next approver), and edits add revisions — so both the draft
    // and the history are refreshed on every contract mutation.
    void qc.invalidateQueries({
      queryKey: ["contracts", data.id, "document-draft"],
    });
    void qc.invalidateQueries({
      queryKey: ["contracts", data.id, "document-revisions"],
    });
  };

  const staffAccept = useMutation({
    mutationFn: (payload: {
      validityDays: number;
      documentSnapshot?: Freight.IContractDocumentSnapshot;
      validFrom?: string;
      validUntil?: string;
    }) =>
      contractsService.staffAccept(
        contractId,
        payload.validityDays,
        payload.documentSnapshot,
        { validFrom: payload.validFrom, validUntil: payload.validUntil },
      ),
    onSuccess: (data) => onSuccess(data, "Contract accepted for approval"),
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to accept contract")),
  });

  // Edit THIS contract's document articles (per-contract; never the templates).
  const updateDocument = useMutation({
    mutationFn: (snapshot: Freight.IContractDocumentSnapshot) =>
      contractsService.updateContractDocument(contractId, snapshot),
    onSuccess: (data) => onSuccess(data, "Contract document updated"),
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to update contract document")),
  });

  const requestChanges = useMutation({
    mutationFn: (note: string) =>
      contractsService.requestChanges(contractId, note),
    onSuccess: (data) => onSuccess(data, "Changes requested from customer"),
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to request changes")),
  });

  const reject = useMutation({
    mutationFn: (reason: string) => contractsService.reject(contractId, reason),
    onSuccess: (data) => onSuccess(data, "Contract rejected"),
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to reject contract")),
  });

  const approveStep = useMutation({
    // The server derives the required role from the step itself, so the client
    // does not send one.
    mutationFn: ({ stepId }: { stepId: string }) =>
      contractsService.approveStep({ id: contractId, stepId }),
    onSuccess: (data) => {
      // The final approval is what generates the PDF and moves the contract to
      // CONTRACT_READY — before that approvers review a live preview.
      const message =
        data.status === "CONTRACT_READY"
          ? "Final approval complete — contract generated and ready to sign"
          : "Approval step completed";
      onSuccess(data, message);
    },
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to approve step")),
  });

  // Per-step rejection by an approver (line staff / director / CEO). Two
  // flavours: without returnToStepId it is terminal (REJECTED, customer must
  // resubmit); with it the contract is sent back to that earlier approver and
  // the chain re-runs from there.
  const rejectStep = useMutation({
    mutationFn: ({
      stepId,
      reason,
      returnToStepId,
    }: {
      stepId: string;
      reason: string;
      returnToStepId?: string;
    }) =>
      contractsService.rejectStep({ id: contractId, stepId, reason, returnToStepId }),
    onSuccess: (data, variables) =>
      onSuccess(
        data,
        variables.returnToStepId
          ? "Contract sent back in the approval chain"
          : "Contract rejected",
      ),
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to reject step")),
  });

  // Manual fallback generate — used only if auto-generation failed.
  const generateContract = useMutation({
    mutationFn: () => contractsService.generateContract(contractId),
    onSuccess: (data) => onSuccess(data, "Contract generated"),
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to generate contract")),
  });

  const signContract = useMutation({
    mutationFn: (payload: SignContractPayload) =>
      contractsService.signContract(contractId, payload),
    onSuccess: (data) => onSuccess(data, "Contract signed"),
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to sign contract")),
  });

  const createBooking = useMutation({
    mutationFn: (payload: Freight.CreateBookingUnderContractDto) =>
      contractsService.createBookingUnderContract(contractId, payload),
    onSuccess: () => {
      toast.success("Booking created under contract");
      void invalidateContractDetail(qc, contractId);
    },
    // Surface the server's reason (e.g. a container already booked on the same
    // train) instead of a generic failure.
    onError: (e: Error) => toast.error(e.message || "Failed to create booking"),
  });

  const completeBooking = useMutation({
    mutationFn: ({
      bookingId,
      payload,
    }: {
      bookingId: string;
      payload: Freight.CreateBookingUnderContractDto;
    }) =>
      contractsService.completeBookingUnderContract(
        contractId,
        bookingId,
        payload,
      ),
    onSuccess: () => {
      toast.success("Booking completed");
      void invalidateContractDetail(qc, contractId);
    },
    onError: (e: Error) =>
      toast.error(e.message || "Failed to complete booking"),
  });

  const isPending =
    staffAccept.isPending ||
    updateDocument.isPending ||
    requestChanges.isPending ||
    reject.isPending ||
    approveStep.isPending ||
    rejectStep.isPending ||
    generateContract.isPending ||
    signContract.isPending ||
    createBooking.isPending;

  return {
    staffAccept,
    updateDocument,
    requestChanges,
    reject,
    approveStep,
    rejectStep,
    generateContract,
    signContract,
    createBooking,
    completeBooking,
    isPending,
  };
}

/**
 * Pre-booking clearance mutations keyed on a contract. Pass `selfClear = true`
 * for Path A (non-customs) contracts so review/finalize hit the Operations
 * endpoints instead of the GL ET ones. Path A has no GL output upload step.
 */
export function useContractClearanceMutations(
  contractId: string,
  selfClear = false,
) {
  const qc = useQueryClient();

  const refresh = () => {
    void qc.invalidateQueries({
      queryKey: QUERY_KEYS.CONTRACTS.clearance(contractId),
    });
    void qc.invalidateQueries({
      queryKey: ["contracts", "clearance-queue"],
    });
    void invalidateContractDetail(qc, contractId);
  };

  const reviewDocument = useMutation({
    mutationFn: (p: {
      fileKey: string;
      status: "APPROVED" | "QUERIED";
      note?: string;
    }) =>
      selfClear
        ? contractsService.opsReviewClearanceDocument(contractId, p)
        : contractsService.reviewClearanceDocument(contractId, p),
    onSuccess: (_d, p) => {
      toast.success(
        p.status === "APPROVED"
          ? "Document approved"
          : "Query sent to customer",
      );
      refresh();
    },
    onError: (e) =>
      toast.error(
        e instanceof Error ? e.message : "Could not update document",
      ),
  });

  // Approve every still-pending customer document in one click. There is no
  // server-side bulk endpoint, so fan out the single-document review calls and
  // refresh once after they all settle.
  const approveAll = useMutation({
    mutationFn: async (fileKeys: string[]) => {
      const review = selfClear
        ? contractsService.opsReviewClearanceDocument
        : contractsService.reviewClearanceDocument;
      await Promise.all(
        fileKeys.map((fileKey) =>
          review(contractId, { fileKey, status: "APPROVED" }),
        ),
      );
    },
    onSuccess: (_d, fileKeys) => {
      toast.success(
        `${fileKeys.length} document${fileKeys.length === 1 ? "" : "s"} approved`,
      );
      refresh();
    },
    onError: (error) => toast.error(extractErrorMessage(error, "Could not approve all documents")),
  });

  const uploadOutputDocuments = useMutation({
    mutationFn: (files: Record<string, File | null>) =>
      contractsService.uploadClearanceOutput(contractId, files),
    onSuccess: () => {
      toast.success("Output documents uploaded");
      refresh();
    },
    onError: (error) => toast.error(extractErrorMessage(error, "Upload failed")),
  });

  const finalizeClearance = useMutation({
    mutationFn: () =>
      selfClear
        ? contractsService.opsFinalizeClearance(contractId)
        : contractsService.finalizeClearance(contractId),
    onSuccess: () => {
      toast.success(
        selfClear
          ? "Document approval finalized — customer can now book"
          : "Document approval finalized — ready for booking",
      );
      refresh();
    },
    onError: (e) =>
      toast.error(
        e instanceof Error
          ? e.message
          : "Could not finalize document approval",
      ),
  });

  return { reviewDocument, approveAll, uploadOutputDocuments, finalizeClearance };
}

/** Complete a post-booking GL milestone. */
export function useCompleteMilestone(bookingId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ code, note }: { code: string; note?: string }) =>
      contractsService.completeMilestone(bookingId, code, note),
    onSuccess: () => {
      toast.success("Milestone completed");
      void qc.invalidateQueries({
        queryKey: QUERY_KEYS.CONTRACTS.bookingMilestones(bookingId),
      });
    },
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to complete milestone")),
  });
}

function invalidateMilestones(qc: QueryClient, bookingId: string) {
  void qc.invalidateQueries({
    queryKey: QUERY_KEYS.CONTRACTS.bookingMilestones(bookingId),
  });
}

/** Assign a customs risk level (completes RISK_ASSIGNED). */
export function useAssignRisk(bookingId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (payload: {
      riskLevel: Freight.CustomsRiskLevel;
      note?: string;
    }) => contractsService.assignRisk(bookingId, payload),
    onSuccess: () => {
      toast.success("Customs risk assigned");
      invalidateMilestones(qc, bookingId);
    },
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to assign risk")),
  });
}

/** Advise duty & tax (completes DUTY_TAXES_ADVISED). */
export function useAdviseDuty(bookingId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (payload: {
      amount: number;
      currency: string;
      declarationSerial?: string;
      note?: string;
    }) => contractsService.adviseDuty(bookingId, payload),
    onSuccess: () => {
      toast.success("Duty & tax advised to customer");
      invalidateMilestones(qc, bookingId);
    },
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to advise duty & tax")),
  });
}

/** Route the shipment to a station + bind GL staff. */
export function useAssignStation(bookingId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (payload: { stationYardId: string; staffId?: string }) =>
      contractsService.assignStation(bookingId, payload),
    onSuccess: () => {
      toast.success("Shipment routed to station");
      void qc.invalidateQueries({
        queryKey: QUERY_KEYS.BOOKINGS.byId(bookingId),
      });
    },
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to assign station")),
  });
}

/** Upload GL post-booking documents (DO/RO/T1/…); auto-completes milestones. */
export function useUploadGlDocuments(bookingId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (files: Record<string, File | null>) =>
      contractsService.uploadGlDocuments(bookingId, files),
    onSuccess: (res) => {
      const n = res.completedMilestones.length;
      toast.success(
        n > 0
          ? `Uploaded — ${n} milestone${n === 1 ? "" : "s"} advanced`
          : "Documents uploaded",
      );
      invalidateMilestones(qc, bookingId);
    },
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to upload documents")),
  });
}

/** Incidents for a shipment (damage / exceptions). */
export function useBookingIncidents(bookingId: string | undefined) {
  return useQuery({
    queryKey: QUERY_KEYS.CONTRACTS.bookingIncidents(bookingId ?? ""),
    queryFn: () => contractsService.listIncidents(bookingId!),
    enabled: !!bookingId,
  });
}

/** Report a cargo exception with photos. */
export function useReportIncident(bookingId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (payload: {
      incidentType: Freight.IncidentType;
      description: string;
      photos: File[];
    }) => contractsService.reportIncident(bookingId, payload),
    onSuccess: () => {
      toast.success("Incident reported");
      void qc.invalidateQueries({
        queryKey: QUERY_KEYS.CONTRACTS.bookingIncidents(bookingId),
      });
    },
    onError: (error) => toast.error(extractErrorMessage(error, "Failed to report incident")),
  });
}
