import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
  fireEvent,
  render,
  screen,
  waitFor,
  within,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import ComplaintModal from "@/components/complaints/ComplaintModal";
import type { ApiEnvelope } from "@/lib/api/types";
import { MAX_UPLOAD_SIZE_BYTES } from "@/lib/upload/fileUtils";
import { array, string } from "yup";

const { requireImagesValidation } = vi.hoisted(() => ({
  requireImagesValidation: { enabled: false },
}));

const alertMock = vi.fn();

vi.mock("@/hooks/useValidation", async (importOriginal) => {
  const actual =
    await importOriginal<typeof import("@/hooks/useValidation")>();

  return {
    useValidation: () => {
      if (requireImagesValidation.enabled) {
        const { validation } = actual.useValidation();
        return {
          validation: {
            ...validation,
            files_optional: () =>
              array().min(1, "خطأ في المرفقات").of(string().required()),
          },
        };
      }

      return actual.useValidation();
    },
  };
});

vi.mock("@/lib/api", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@/lib/api")>();
  return {
    ...actual,
    apiFetch: vi.fn(),
  };
});

import { apiFetch } from "@/lib/api";

interface ComplaintDetails {
  id: number | string;
  title: string;
  description: string;
  complain_number: string;
  created_at: string;
  status: string;
  status_translation: string;
  images: string[];
  reply: string | null;
}

function getBackdrop(): HTMLElement {
  return screen.getByRole("button", { name: "إغلاق النافذة" });
}

function getCloseButton(): HTMLElement {
  const icon = document.querySelector(".ri-close-line");
  if (!icon?.parentElement) {
    throw new Error("Close button not found");
  }
  return icon.parentElement as HTMLElement;
}

function getTitleInput() {
  return screen.getByPlaceholderText("اكتب عنواناً مختصراً");
}

function getDescriptionInput() {
  return screen.getByPlaceholderText("اشرح المشكلة بالتفصيل...");
}

function getSubmitButton() {
  return screen.getByRole("button", { name: "إرسال الشكوى" });
}

function getDocumentFileInput(): HTMLInputElement {
  const dropzone = screen.getByRole("button", { name: /ارفق المستندات/i });
  const input = dropzone.querySelector('input[type="file"]');
  if (!input) {
    throw new Error("Document file input not found");
  }
  return input as HTMLInputElement;
}

function uploadDocument(file: File) {
  fireEvent.change(getDocumentFileInput(), { target: { files: [file] } });
}

function makeFile(
  name: string,
  options: { type?: string; size?: number } = {},
): File {
  const { type = "application/pdf", size = 2048 } = options;
  const file = new File([new Uint8Array(Math.min(size, 1024))], name, { type });
  if (size !== file.size) {
    Object.defineProperty(file, "size", { value: size });
  }
  return file;
}

function buildComplaintDetails(
  overrides: Partial<ComplaintDetails> = {},
): ComplaintDetails {
  return {
    id: 1,
    title: "مشكلة في المركبة",
    description: "وصف تفصيلي للشكوى المقدمة",
    complain_number: "CMP-1001",
    created_at: "2026-01-15T10:00:00.000Z",
    status: "new",
    status_translation: "جديدة",
    images: ["https://example.com/attachment.jpg"],
    reply: "تمت مراجعة الشكوى",
    ...overrides,
  };
}

async function fillValidComplaintForm(
  user: ReturnType<typeof userEvent.setup>,
) {
  await user.type(getTitleInput(), "عنوان الشكوى");
  await user.type(
    getDescriptionInput(),
    "وصف مفصل للشكوى يتجاوز عشرة أحرف على الأقل",
  );
}

describe("ComplaintModal", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.stubGlobal("alert", alertMock);
  });

  afterEach(() => {
    requireImagesValidation.enabled = false;
    vi.unstubAllGlobals();
  });

  it("renders nothing when the modal is closed", () => {
    const { container } = render(
      <ComplaintModal isOpen={false} mode="create" onClose={vi.fn()} />,
    );

    expect(container).toBeEmptyDOMElement();
  });

  it("closes on Escape key press on the backdrop", () => {
    const onClose = vi.fn();

    render(<ComplaintModal isOpen mode="create" onClose={onClose} />);

    fireEvent.keyDown(getBackdrop(), { key: "Escape" });

    expect(onClose).toHaveBeenCalledTimes(1);
  });

  it("does not close when a non-Escape key is pressed on the backdrop", () => {
    const onClose = vi.fn();

    render(<ComplaintModal isOpen mode="create" onClose={onClose} />);

    fireEvent.keyDown(getBackdrop(), { key: "Tab" });

    expect(onClose).not.toHaveBeenCalled();
  });

  it("displays image field validation errors when images fail validation", async () => {
    const user = userEvent.setup();
    requireImagesValidation.enabled = true;

    render(<ComplaintModal isOpen mode="create" onClose={vi.fn()} />);
    await fillValidComplaintForm(user);
    await user.click(getSubmitButton());

    expect(await screen.findByText("خطأ في المرفقات")).toBeInTheDocument();
    expect(apiFetch).not.toHaveBeenCalled();
  });

  it("closes when the backdrop itself is clicked", () => {
    const onClose = vi.fn();

    render(<ComplaintModal isOpen mode="create" onClose={onClose} />);

    fireEvent.click(getBackdrop());

    expect(onClose).toHaveBeenCalledTimes(1);
  });

  it("closes when the header close button is clicked", async () => {
    const user = userEvent.setup();
    const onClose = vi.fn();

    render(<ComplaintModal isOpen mode="create" onClose={onClose} />);

    await user.click(getCloseButton());

    expect(onClose).toHaveBeenCalledTimes(1);
  });

  it("does not close when clicking inside modal content", () => {
    const onClose = vi.fn();

    render(<ComplaintModal isOpen mode="create" onClose={onClose} />);

    fireEvent.click(screen.getByText("إضافة شكوى جديدة"));

    expect(onClose).not.toHaveBeenCalled();
  });

  it("renders the shared FileDropzone in create mode", () => {
    render(<ComplaintModal isOpen mode="create" onClose={vi.fn()} />);

    expect(screen.getByText("ارفق المستندات")).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: /ارفق المستندات/i }),
    ).toBeInTheDocument();
  });

  it("rejects empty required fields in create mode", async () => {
    const user = userEvent.setup();

    render(<ComplaintModal isOpen mode="create" onClose={vi.fn()} />);

    await user.click(getSubmitButton());

    expect(
      await screen.findByText("الرجاء إدخال العنوان"),
    ).toBeInTheDocument();
    expect(apiFetch).not.toHaveBeenCalled();
  });

  it("rejects a description that is too short in create mode", async () => {
    const user = userEvent.setup();

    render(<ComplaintModal isOpen mode="create" onClose={vi.fn()} />);

    await user.type(getTitleInput(), "عنوان الشكوى");
    await user.type(getDescriptionInput(), "قصير");
    await user.click(getSubmitButton());

    expect(
      await screen.findByText("الوصف يجب أن يكون 10 أحرف على الأقل"),
    ).toBeInTheDocument();
    expect(apiFetch).not.toHaveBeenCalled();
  });

  it("submits a valid complaint with FormData and closes on success", async () => {
    const user = userEvent.setup();
    const onClose = vi.fn();
    const onSuccess = vi.fn();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope = { key: "success", msg: "" };
      options?.successCase?.(envelope);
      return envelope;
    });

    render(
      <ComplaintModal
        isOpen
        mode="create"
        onClose={onClose}
        onSuccess={onSuccess}
      />,
    );

    await fillValidComplaintForm(user);
    const attachment = makeFile("proof.pdf");
    uploadDocument(attachment);
    await user.click(getSubmitButton());

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledWith("/provider/complains", {
        method: "POST",
        body: expect.any(FormData),
        getSuccess: true,
        successCase: expect.any(Function),
        errorCase: expect.any(Function),
      });
    });

    const body = vi.mocked(apiFetch).mock.calls[0][1]?.body as FormData;
    expect(body.get("title")).toBe("عنوان الشكوى");
    expect(body.get("description")).toBe(
      "وصف مفصل للشكوى يتجاوز عشرة أحرف على الأقل",
    );
    expect(body.getAll("images[]")).toHaveLength(1);

    expect(onSuccess).toHaveBeenCalledTimes(1);
    expect(onClose).toHaveBeenCalledTimes(1);
  });

  it("shows a loading state while submitting a complaint", async () => {
    const user = userEvent.setup();
    let resolveFetch!: (envelope: ApiEnvelope) => void;

    vi.mocked(apiFetch).mockImplementation(
      (_url, options) =>
        new Promise<ApiEnvelope>((resolve) => {
          resolveFetch = (envelope) => {
            options?.successCase?.(envelope);
            resolve(envelope);
          };
        }),
    );

    render(<ComplaintModal isOpen mode="create" onClose={vi.fn()} />);
    await fillValidComplaintForm(user);
    await user.click(getSubmitButton());

    expect(
      screen.getByRole("button", { name: /جاري الإرسال/ }),
    ).toBeDisabled();

    resolveFetch({ key: "success", msg: "" });
    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalledTimes(1);
    });
  });

  it("shows an API error and stays open when submission fails", async () => {
    const user = userEvent.setup();
    const onClose = vi.fn();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope = { key: "fail", msg: "فشل إرسال الشكوى" };
      options?.errorCase?.(envelope);
      return envelope;
    });

    render(<ComplaintModal isOpen mode="create" onClose={onClose} />);
    await fillValidComplaintForm(user);
    await user.click(getSubmitButton());

    expect(await screen.findByText("فشل إرسال الشكوى")).toBeInTheDocument();
    expect(onClose).not.toHaveBeenCalled();
    expect(getSubmitButton()).not.toBeDisabled();
  });

  it("falls back to a default error message when submission fails without msg", async () => {
    const user = userEvent.setup();

    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope = { key: "fail", msg: "" };
      options?.errorCase?.(envelope);
      return envelope;
    });

    render(<ComplaintModal isOpen mode="create" onClose={vi.fn()} />);
    await fillValidComplaintForm(user);
    await user.click(getSubmitButton());

    expect(
      await screen.findByText("حدث خطأ، يرجى المحاولة مرة أخرى"),
    ).toBeInTheDocument();
  });

  it("accepts an allowed upload and allows removing it", async () => {
    const user = userEvent.setup();

    render(<ComplaintModal isOpen mode="create" onClose={vi.fn()} />);

    const pdf = makeFile("attachment.pdf");
    uploadDocument(pdf);

    expect(await screen.findByText("attachment.pdf")).toBeInTheDocument();

    const fileRow = screen.getByText("attachment.pdf").closest(
      ".flex.items-center",
    ) as HTMLElement;
    await user.click(within(fileRow).getByRole("button"));

    expect(screen.queryByText("attachment.pdf")).not.toBeInTheDocument();
  });

  it("rejects a disallowed file type with an alert", () => {
    render(<ComplaintModal isOpen mode="create" onClose={vi.fn()} />);

    uploadDocument(makeFile("notes.exe", { type: "application/octet-stream" }));

    expect(alertMock).toHaveBeenCalledWith(
      "صيغة الملف غير مدعومة. الصيغ المسموح بها: PDF, JPG, PNG",
    );
    expect(screen.queryByText("notes.exe")).not.toBeInTheDocument();
  });

  it("rejects an oversized file with an alert", () => {
    render(<ComplaintModal isOpen mode="create" onClose={vi.fn()} />);

    uploadDocument(
      makeFile("huge.pdf", {
        type: "application/pdf",
        size: MAX_UPLOAD_SIZE_BYTES + 1,
      }),
    );

    expect(alertMock).toHaveBeenCalledWith(
      "حجم الملف يجب ألا يتجاوز 10 ميجابايت",
    );
    expect(screen.queryByText("huge.pdf")).not.toBeInTheDocument();
  });

  it("accepts a file dropped onto the upload area", () => {
    render(<ComplaintModal isOpen mode="create" onClose={vi.fn()} />);

    const dropzone = screen.getByRole("button", { name: /ارفق المستندات/i });
    const dropped = makeFile("dropped.png", { type: "image/png" });

    fireEvent.drop(dropzone, {
      dataTransfer: { files: [dropped] },
    });

    expect(screen.getByText("dropped.png")).toBeInTheDocument();
  });

  it("loads and displays complaint details in view mode", async () => {
    const details = buildComplaintDetails();

    vi.mocked(apiFetch).mockImplementation(async (url, options) => {
      const envelope: ApiEnvelope<ComplaintDetails> = {
        key: "success",
        data: details,
        msg: "",
      };
      options?.successCase?.(envelope);
      return envelope;
    });

    render(
      <ComplaintModal
        isOpen
        mode="view"
        complaintId={1}
        onClose={vi.fn()}
      />,
    );

    expect(screen.getByText("تفاصيل الشكوى")).toBeInTheDocument();

    expect(await screen.findByText(details.title)).toBeInTheDocument();
    expect(screen.getByText(details.description)).toBeInTheDocument();
    expect(screen.getByText(`رقم الشكوى: ${details.complain_number}`)).toBeInTheDocument();
    expect(screen.getByText(`● ${details.status_translation}`)).toBeInTheDocument();
    expect(screen.getByText("رد الإدارة")).toBeInTheDocument();
    expect(screen.getByText(details.reply!)).toBeInTheDocument();
    expect(screen.getByAltText("مرفق")).toBeInTheDocument();

    expect(apiFetch).toHaveBeenCalledWith("/provider/complains/1", {
      method: "GET",
      successCase: expect.any(Function),
      errorCase: expect.any(Function),
    });
  });

  it("shows a fetch error in view mode", async () => {
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope = { key: "fail", msg: "تعذر الجلب" };
      options?.errorCase?.(envelope);
      return envelope;
    });

    render(
      <ComplaintModal
        isOpen
        mode="view"
        complaintId={99}
        onClose={vi.fn()}
      />,
    );

    expect(await screen.findByText("تعذر الجلب")).toBeInTheDocument();
  });

  it("falls back to a default fetch error in view mode", async () => {
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope = { key: "fail", msg: "" };
      options?.errorCase?.(envelope);
      return envelope;
    });

    render(
      <ComplaintModal
        isOpen
        mode="view"
        complaintId={99}
        onClose={vi.fn()}
      />,
    );

    expect(await screen.findByText("خطأ في جلب التفاصيل")).toBeInTheDocument();
  });

  it.each([
    ["in_progress", "قيد المعالجة"],
    ["resolved", "تم الحل"],
    ["closed", "مغلقة"],
    ["unknown", "غير معروف"],
  ] as const)(
    "renders the %s status styling in view mode",
    async (status, statusTranslation) => {
      vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
        const envelope: ApiEnvelope<ComplaintDetails> = {
          key: "success",
          data: buildComplaintDetails({
            status,
            status_translation: statusTranslation,
            images: [],
            reply: null,
          }),
          msg: "",
        };
        options?.successCase?.(envelope);
        return envelope;
      });

      render(
        <ComplaintModal
          isOpen
          mode="view"
          complaintId={status}
          onClose={vi.fn()}
        />,
      );

      expect(
        await screen.findByText(`● ${statusTranslation}`),
      ).toBeInTheDocument();
    },
  );

  it("hides the admin reply section when no reply exists", async () => {
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope<ComplaintDetails> = {
        key: "success",
        data: buildComplaintDetails({ reply: null, images: [] }),
        msg: "",
      };
      options?.successCase?.(envelope);
      return envelope;
    });

    render(
      <ComplaintModal
        isOpen
        mode="view"
        complaintId={1}
        onClose={vi.fn()}
      />,
    );

    await screen.findByText("مشكلة في المركبة");
    expect(screen.queryByText("رد الإدارة")).not.toBeInTheDocument();
  });

  it("hides broken attachment images in view mode", async () => {
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope<ComplaintDetails> = {
        key: "success",
        data: buildComplaintDetails({
          images: ["https://example.com/broken.jpg"],
          reply: null,
        }),
        msg: "",
      };
      options?.successCase?.(envelope);
      return envelope;
    });

    render(
      <ComplaintModal
        isOpen
        mode="view"
        complaintId={1}
        onClose={vi.fn()}
      />,
    );

    const image = await screen.findByAltText("مرفق");
    fireEvent.error(image);

    expect(image.style.display).toBe("none");
  });

  it("ignores a view success response without data", async () => {
    vi.mocked(apiFetch).mockImplementation(async (_url, options) => {
      const envelope: ApiEnvelope = { key: "success", msg: "" };
      options?.successCase?.(envelope);
      return envelope;
    });

    render(
      <ComplaintModal
        isOpen
        mode="view"
        complaintId={1}
        onClose={vi.fn()}
      />,
    );

    await waitFor(() => {
      expect(apiFetch).toHaveBeenCalled();
    });
    expect(screen.queryByText("مشكلة في المركبة")).not.toBeInTheDocument();
  });

  it("ignores stale view fetch callbacks after the modal unmounts", async () => {
    let resolveFetch!: (value: ApiEnvelope<ComplaintDetails>) => void;

    vi.mocked(apiFetch).mockImplementation((_url, options) => {
      return new Promise<ApiEnvelope<ComplaintDetails>>((resolve) => {
        resolveFetch = (envelope) => {
          if (envelope.key === "fail") {
            options?.errorCase?.(envelope);
          } else {
            options?.successCase?.(envelope);
          }
          resolve(envelope);
        };
      });
    });

    const { unmount } = render(
      <ComplaintModal
        isOpen
        mode="view"
        complaintId={1}
        onClose={vi.fn()}
      />,
    );

    await waitFor(() => expect(apiFetch).toHaveBeenCalled());
    unmount();
    resolveFetch({ key: "fail", msg: "متأخر" });

    expect(screen.queryByText("متأخر")).not.toBeInTheDocument();
  });
});
