"use client";

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { bulkUploadKeys } from "./keys";
import { isTerminalStatus, type BulkUpload, type ProgressView } from "./types";

async function parseError(res: Response, fallback: string): Promise<Error> {
  const body = await res.json().catch(() => ({}));
  return new Error(body?.error ?? fallback);
}

export type CreateBulkUploadInput = {
  file: File;
  portfolioId: string;
  userId: string;
  sourceHeaders: string[];
  contentType: string;
  fileKey: string;
  onProgress?: (percent: number) => void;
};

export type CreateBulkUploadResult = {
  id: string;
  s3Key: string;
  s3Bucket: string;
  status: string;
};

export function useCreateBulkUpload() {
  return useMutation<CreateBulkUploadResult, Error, CreateBulkUploadInput>({
    mutationFn: async (input) => {
      const presignRes = await fetch("/api/upload/bulk-addresses", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          userId: input.userId,
          portfolioId: input.portfolioId,
          fileKey: input.fileKey,
          contentType: input.contentType,
        }),
      });
      if (!presignRes.ok) throw await parseError(presignRes, "Failed to generate upload URL.");
      const { url: presignedUrl } = await presignRes.json();

      await new Promise<void>((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open("PUT", presignedUrl);
        xhr.setRequestHeader("Content-Type", input.contentType);
        if (input.onProgress) {
          xhr.upload.addEventListener("progress", (e) => {
            if (e.lengthComputable) {
              input.onProgress!(Math.round((e.loaded / e.total) * 100));
            }
          });
        }
        xhr.onload = () => {
          if (xhr.status >= 200 && xhr.status < 300) resolve();
          else reject(new Error(`S3 upload failed: ${xhr.status}`));
        };
        xhr.onerror = () => reject(new Error("Network error during upload"));
        xhr.send(input.file);
      });

      const confirmRes = await fetch("/api/upload/bulk-addresses/confirm", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          fileKey: input.fileKey,
          filename: input.file.name,
          portfolioId: input.portfolioId,
          userId: input.userId,
          sourceHeaders: input.sourceHeaders,
        }),
      });
      if (!confirmRes.ok) throw await parseError(confirmRes, "Failed to record upload.");
      return confirmRes.json();
    },
  });
}

export function useSetColumnMapping(portfolioId: string, uploadId: string) {
  const queryClient = useQueryClient();
  return useMutation<BulkUpload, Error, { columnMapping: Record<string, string> }>({
    mutationFn: async (input) => {
      const res = await fetch(`/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(input),
      });
      if (!res.ok) throw await parseError(res, "Failed to save mapping.");
      return res.json();
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: bulkUploadKeys.progress(uploadId) });
    },
  });
}

// field -> description -> resolved enum, for the multi-entry sample (issue #298).
export type SampleClassifications = Record<string, Record<string, string>>;

export function useEditClassification(portfolioId: string, uploadId: string) {
  const queryClient = useQueryClient();
  return useMutation<void, Error, { field: string; description: string; value: string }>({
    mutationFn: async (input) => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/classifications`,
        {
          method: "PATCH",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(input),
        },
      );
      if (!res.ok) throw await parseError(res, "Failed to save classification.");
    },
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: [...bulkUploadKeys.progress(uploadId), "classifications"],
      });
    },
  });
}

// Sample classifications for the review panels PLUS the still-`Unknown`
// descriptions that gate Finalise (ADR-0006).
export interface ClassificationsView {
  classifications: SampleClassifications;
  unknown: Record<string, string[]>;
}

export function useSampleClassifications(
  portfolioId: string,
  uploadId: string,
  enabled: boolean,
) {
  return useQuery<ClassificationsView, Error>({
    queryKey: [...bulkUploadKeys.progress(uploadId), "classifications"],
    enabled,
    queryFn: async () => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/classifications`,
      );
      if (!res.ok) throw await parseError(res, "Failed to load classifications.");
      const body = await res.json();
      return {
        classifications: (body.classifications ?? {}) as SampleClassifications,
        unknown: (body.unknown ?? {}) as Record<string, string[]>,
      };
    },
  });
}

// One combiner row that address2uprn could not confidently match (ADR-0057),
// plus any saved correction. Mirrors the server FlaggedAddressRow.
export interface FlaggedAddress {
  sourceRowId: string;
  address: string;
  postcode: string;
  status: string;
  lexiscore: number | null;
  suggestedUprn: string | null;
  resolvedUprn: string | null;
  resolvedAddress: string | null;
  markedNoUprn: boolean;
  resolved: boolean;
}

export interface AddressMatchesView {
  flagged: FlaggedAddress[];
  unresolved: number;
}

export function useAddressMatches(
  portfolioId: string,
  uploadId: string,
  enabled: boolean,
) {
  return useQuery<AddressMatchesView, Error>({
    queryKey: [...bulkUploadKeys.progress(uploadId), "address-matches"],
    enabled,
    queryFn: async () => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/address-matches`,
      );
      if (!res.ok) throw await parseError(res, "Failed to load address matches.");
      const body = await res.json();
      return {
        flagged: (body.flagged ?? []) as FlaggedAddress[],
        unresolved: Number(body.unresolved ?? 0),
      };
    },
  });
}

export type SaveCorrectionInput =
  | { sourceRowId: string; uprn: string; address: string; postcode: string }
  | { sourceRowId: string; markedNoUprn: true };

export function useSaveUprnCorrection(portfolioId: string, uploadId: string) {
  const queryClient = useQueryClient();
  return useMutation<void, Error, SaveCorrectionInput>({
    mutationFn: async (input) => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/address-corrections`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(input),
        },
      );
      if (!res.ok) throw await parseError(res, "Failed to save address.");
    },
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: [...bulkUploadKeys.progress(uploadId), "address-matches"],
      });
    },
  });
}

export function useConfirmMultiEntryOrdering(portfolioId: string, uploadId: string) {
  const queryClient = useQueryClient();
  return useMutation<BulkUpload, Error, { permutations: Record<string, number[]> }>({
    mutationFn: async (input) => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/multi-entry-ordering`,
        {
          method: "PATCH",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(input),
        },
      );
      if (!res.ok) throw await parseError(res, "Failed to save ordering.");
      return res.json();
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: bulkUploadKeys.progress(uploadId) });
    },
  });
}

// Records the "Verify classification" acknowledgement (ADR-0004 Step 1),
// unlocking Finalise. Per-row corrections go through useEditClassification.
export function useConfirmVerification(portfolioId: string, uploadId: string) {
  const queryClient = useQueryClient();
  return useMutation<BulkUpload, Error, void>({
    mutationFn: async () => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/verify-classification`,
        { method: "PATCH" },
      );
      if (!res.ok) throw await parseError(res, "Failed to confirm classification.");
      return res.json();
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: bulkUploadKeys.progress(uploadId) });
    },
  });
}

export function useStartAddressMatching(portfolioId: string, uploadId: string) {
  const queryClient = useQueryClient();
  return useMutation<{ taskId: string }, Error, void>({
    mutationFn: async () => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/start-address-matching`,
        { method: "POST" },
      );
      if (!res.ok) throw await parseError(res, "Failed to start address matching.");
      return res.json();
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: bulkUploadKeys.progress(uploadId) });
    },
  });
}

export function useBulkUploadProgress(
  portfolioId: string,
  uploadId: string,
  options?: { onSuccess?: (data: ProgressView) => void },
) {
  return useQuery<ProgressView, Error>({
    queryKey: bulkUploadKeys.progress(uploadId),
    queryFn: async () => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/progress`,
      );
      if (!res.ok) throw await parseError(res, "Failed to load progress.");
      return res.json();
    },
    refetchInterval: (data) => {
      const status = data?.upload.status;
      return status && isTerminalStatus(status) ? false : 3000;
    },
    // v4 onSuccess fires after each successful poll; callers use it to react to a
    // status transition (e.g. refresh the server page once it goes terminal).
    onSuccess: options?.onSuccess,
  });
}

export function useRequestCombine(portfolioId: string, uploadId: string) {
  const queryClient = useQueryClient();
  return useMutation<unknown, Error, void>({
    mutationFn: async () => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/combine`,
        { method: "POST" },
      );
      if (!res.ok) throw await parseError(res, "Failed to start combiner.");
      return res.json();
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: bulkUploadKeys.progress(uploadId) });
    },
  });
}

export function useFinalize(portfolioId: string, uploadId: string) {
  const queryClient = useQueryClient();
  return useMutation<void, Error, void>({
    mutationFn: async () => {
      const res = await fetch(
        `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/finalize`,
        { method: "POST" },
      );
      if (!res.ok) throw await parseError(res, "Finalize failed.");
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: bulkUploadKeys.progress(uploadId) });
    },
  });
}
