import React from "react";
import "@testing-library/jest-dom";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { ConfirmProvider } from "./confirm";
import { PromptProvider } from "./prompt";
import { ToastProvider } from "./toast";
import { I18nProvider } from "./i18n/I18nProvider";
import Correspondence from "./components/Correspondence";
import { api } from "./api";

jest.setTimeout(15000);

jest.mock("./api", () => ({
  api: {
    get: jest.fn(),
    post: jest.fn(),
    put: jest.fn(),
    patch: jest.fn(),
    delete: jest.fn(),
    interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
  },
}));

const mockedApi = api as jest.Mocked<typeof api>;

function renderDialog() {
  return render(
    <ToastProvider>
      <I18nProvider>
        <ConfirmProvider>
          <PromptProvider>
            <Correspondence
              jobId={42}
              jobContext={{ jobTitle: "Backend Developer", companyName: "Acme", recruiterEmail: "maria@acme.test" }}
            />
          </PromptProvider>
        </ConfirmProvider>
      </I18nProvider>
    </ToastProvider>,
  );
}

describe("correspondence Gmail import", () => {
  let correspondenceMessages: any[];

  beforeEach(() => {
    Object.assign(navigator, {
      clipboard: {
        writeText: jest.fn().mockResolvedValue(undefined),
      },
    });
    window.open = jest.fn();

    correspondenceMessages = [];

    mockedApi.get.mockImplementation((url: string, config?: any) => {
      if (url === "/jobapplications/choices") {
        return Promise.resolve({
          data: [
              {
                id: 42,
                jobTitle: "Backend Developer",
                companyName: "Acme",
              },
              {
                id: 77,
                jobTitle: "Platform Engineer",
                companyName: "Beta",
              },
            ],
        } as any);
      }
      if (url === "/jobapplications/42") {
        return Promise.resolve({
          data: {
            id: 42,
            jobTitle: "Backend Developer",
            status: "Applied",
            dateApplied: new Date().toISOString(),
            daysSince: 3,
            company: { name: "Acme", recruiterEmail: "maria@acme.test", recruiterName: "Maria Recruiter" },
            tailoredCvText: "",
            shortSummary: "summary",
          },
        } as any);
      }
      if (url === "/auth/me") {
        return Promise.resolve({ data: { roles: [], profileCvText: "Master CV text" } } as any);
      }
      if (url === "/jobapplications/42/history") {
        return Promise.resolve({ data: [] } as any);
      }
      if (url === "/attachments/42") {
        return Promise.resolve({ data: [] } as any);
      }
      if (url === "/correspondence/42") {
        return Promise.resolve({ data: correspondenceMessages } as any);
      }
      if (url === "/gmail/status") {
        return Promise.resolve({ data: { connected: true, gmailAddress: "user@example.test", lastSyncedAt: new Date().toISOString(), lastSyncAttemptedAt: new Date().toISOString(), lastSyncMode: "list-messages", lastSyncSource: "custom-query", lastSyncStatus: "error", lastSyncError: "Token refresh failed" } } as any);
      }
      if (url === "/gmail/job-candidates") {
        return Promise.resolve({
          data: {
            jobApplicationId: 42,
            jobTitle: "Backend Developer",
            companyName: "Acme",
            recruiterName: "Maria Recruiter",
            recruiterEmail: "maria@acme.test",
            queries: [config?.params?.queryOverride || '"Acme" "Backend Developer" newer_than:365d'],
            threads: [
              {
                threadId: "thread-1",
                subject: "Backend Developer interview",
                score: 42,
                confidence: "high",
                hasImportedMessages: false,
                messageCount: 2,
                latestDate: new Date().toISOString(),
                matchReasons: [
                  { label: "company", value: "Acme" },
                  { label: "recruiterEmail", value: "maria@acme.test" },
                ],
                messages: [
                  {
                    id: "msg-1",
                    threadId: "thread-1",
                    subject: "Backend Developer interview",
                    from: "Maria Recruiter <maria@acme.test>",
                    to: "user@example.test",
                    date: new Date().toISOString(),
                    snippet: "Acme wants to schedule a call.",
                    score: 42,
                    confidence: "high",
                    alreadyImported: false,
                    matchReasons: [
                      { label: "company", value: "Acme" },
                      { label: "jobTitle", value: "Developer" },
                    ],
                  },
                  {
                    id: "msg-2",
                    threadId: "thread-1",
                    subject: "Backend Developer follow-up",
                    from: "user@example.test",
                    to: "Maria Recruiter <maria@acme.test>",
                    date: new Date().toISOString(),
                    snippet: "Following up on the role.",
                    score: 24,
                    confidence: "medium",
                    alreadyImported: false,
                    matchReasons: [{ label: "recency", value: "45d" }],
                  },
                ],
              },
            ],
          },
        } as any);
      }
      return Promise.resolve({ data: [] } as any);
    });

    mockedApi.post.mockImplementation((url: string, body?: any) => {
      if (url === "/gmail/relink-thread") {
        correspondenceMessages = [];
        return Promise.resolve({ data: { threadId: body.threadId, jobApplicationId: body.jobApplicationId, imported: 1, skipped: 0, unlinkedMessages: 1 } } as any);
      }
      if (url === "/gmail/unlink-thread") {
        const removed = correspondenceMessages.filter((message) => message.externalThreadId === body.threadId).length;
        correspondenceMessages = correspondenceMessages.filter((message) => message.externalThreadId !== body.threadId);
        return Promise.resolve({ data: { threadId: body.threadId, jobApplicationId: body.jobApplicationId, removedMessages: removed, decision: body.nextDecision || 'review' } } as any);
      }
      if (url === "/gmail/refresh-linked-threads") {
        const hasReply = correspondenceMessages.some((message) => message.externalMessageId === "msg-2");
        if (!hasReply && correspondenceMessages.some((message) => message.externalThreadId === "thread-1")) {
          correspondenceMessages = [
            ...correspondenceMessages,
            {
              id: 701,
              jobApplicationId: 42,
              from: "Me",
              content: "Following up on the role.",
              subject: "Backend Developer follow-up",
              channel: "Email",
              date: new Date().toISOString(),
              externalMessageId: "msg-2",
              externalThreadId: "thread-1",
              externalFrom: "user@example.test",
              externalTo: "Maria Recruiter <maria@acme.test>",
            },
          ];
          return Promise.resolve({
            data: {
              jobApplicationId: body.jobApplicationId,
              threadsChecked: 1,
              imported: 1,
              skipped: 1,
              hasLinkedThreads: true,
              refreshedAt: new Date().toISOString(),
              threads: [
                {
                  threadId: "thread-1",
                  imported: 1,
                  skipped: 1,
                  totalMessages: 2,
                  status: "imported-new-messages",
                  latestMessageDate: new Date().toISOString(),
                },
              ],
            },
          } as any);
        }

        return Promise.resolve({
          data: {
            jobApplicationId: body.jobApplicationId,
            threadsChecked: correspondenceMessages.some((message) => message.externalThreadId === "thread-1") ? 1 : 0,
            imported: 0,
            skipped: correspondenceMessages.some((message) => message.externalThreadId === "thread-1") ? correspondenceMessages.filter((message) => message.externalThreadId === "thread-1").length : 0,
            hasLinkedThreads: correspondenceMessages.some((message) => message.externalThreadId === "thread-1"),
            refreshedAt: new Date().toISOString(),
            threads: correspondenceMessages.some((message) => message.externalThreadId === "thread-1")
              ? [{ threadId: "thread-1", imported: 0, skipped: correspondenceMessages.filter((message) => message.externalThreadId === "thread-1").length, totalMessages: correspondenceMessages.filter((message) => message.externalThreadId === "thread-1").length, status: "already-current", latestMessageDate: new Date().toISOString() }]
              : [],
          },
        } as any);
      }
      if (url === "/gmail/import") {
        correspondenceMessages = [
          {
            id: 700,
            jobApplicationId: 42,
            from: "Company",
            content: "Acme wants to schedule a call.",
            subject: "Backend Developer interview",
            channel: "Email",
            date: new Date().toISOString(),
            externalMessageId: body.messageId,
            externalThreadId: "thread-1",
            externalFrom: "Maria Recruiter <maria@acme.test>",
            externalTo: "user@example.test",
          },
        ];
        return Promise.resolve({
          data: {
            imported: 1,
            skipped: 0,
            messageId: body.messageId,
            threadId: "thread-1",
            message: correspondenceMessages[0],
          },
        } as any);
      }
      if (url === "/gmail/import-thread") {
        return Promise.resolve({ data: { imported: 2, skipped: 0, threadId: "thread-1" } } as any);
      }
      if (url === "/correspondence") {
        return Promise.resolve({ data: {} } as any);
      }
      return Promise.resolve({ data: {} } as any);
    });

    mockedApi.put.mockResolvedValue({ data: {} } as any);
    mockedApi.delete.mockResolvedValue({ data: {} } as any);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  test("shows ranked Gmail suggestions with reasons and refreshes correspondence after message import", async () => {
    renderDialog();

    fireEvent.click(await screen.findByRole("button", { name: /import email/i }));
    fireEvent.click(await screen.findByRole("tab", { name: /^google$/i }));

    expect((await screen.findAllByText(/backend developer interview/i)).length).toBeGreaterThan(0);
    expect((await screen.findAllByText(/high confidence · score 42/i)).length).toBeGreaterThan(0);
    expect((await screen.findAllByText(/company: acme/i)).length).toBeGreaterThan(0);
    expect(await screen.findByText(/recruiter email: maria@acme\.test/i)).toBeInTheDocument();

    const importButtons = await screen.findAllByRole("button", { name: /^import email$/i });
    fireEvent.click(importButtons[importButtons.length - 1]);

    await waitFor(() => {
      expect(mockedApi.post).toHaveBeenCalledWith("/gmail/import", { jobApplicationId: 42, messageId: "msg-2" });
    });

    expect(await screen.findByText(/thread thread-1/i)).toBeInTheDocument();
    expect(await screen.findByText(/from maria recruiter <maria@acme\.test>/i)).toBeInTheDocument();
    expect(await screen.findByText(/to user@example\.test/i)).toBeInTheDocument();
  });

  test("automatically refreshes already-linked Gmail threads without manual re-import", async () => {
    correspondenceMessages = [
      {
        id: 700,
        jobApplicationId: 42,
        from: "Company",
        content: "Acme wants to schedule a call.",
        subject: "Backend Developer interview",
        channel: "Email",
        date: new Date().toISOString(),
        externalMessageId: "msg-1",
        externalThreadId: "thread-1",
        externalFrom: "Maria Recruiter <maria@acme.test>",
        externalTo: "user@example.test",
      },
    ];

    renderDialog();

    await waitFor(() => {
      expect(mockedApi.post).toHaveBeenCalledWith("/gmail/refresh-linked-threads", { jobApplicationId: 42 });
    });

    expect(await screen.findByText(/backend developer follow-up/i)).toBeInTheDocument();
    expect(await screen.findByText(/following up on the role\./i)).toBeInTheDocument();
    expect((await screen.findAllByText(/thread thread-1/i)).length).toBeGreaterThan(0);
  });

  test("lets the user unlink a linked Gmail thread", async () => {
    correspondenceMessages = [
      {
        id: 700,
        jobApplicationId: 42,
        from: "Company",
        content: "Acme wants to schedule a call.",
        subject: "Backend Developer interview",
        channel: "Email",
        date: new Date().toISOString(),
        externalMessageId: "msg-1",
        externalThreadId: "thread-1",
        externalFrom: "Maria Recruiter <maria@acme.test>",
        externalTo: "user@example.test",
      },
    ];

    renderDialog();

    fireEvent.click(await screen.findByRole("button", { name: /manage thread-1/i }));
    fireEvent.click(await screen.findByRole("button", { name: /unlink from this job/i }));

    await waitFor(() => {
      expect(mockedApi.post).toHaveBeenCalledWith("/gmail/unlink-thread", expect.objectContaining({
        jobApplicationId: 42,
        threadId: "thread-1",
        nextDecision: "review",
      }));
    });

    expect(await screen.findByText(/no messages yet/i)).toBeInTheDocument();
  });

  test("lets the user move a linked Gmail thread to another job", async () => {
    correspondenceMessages = [
      {
        id: 702,
        jobApplicationId: 42,
        from: "Company",
        content: "Second import.",
        subject: "Backend Developer interview",
        channel: "Email",
        date: new Date().toISOString(),
        externalMessageId: "msg-1",
        externalThreadId: "thread-1",
        externalFrom: "Maria Recruiter <maria@acme.test>",
        externalTo: "user@example.test",
      },
    ];

    renderDialog();

    fireEvent.click(await screen.findByRole("button", { name: /manage thread-1/i }));
    fireEvent.mouseDown(await screen.findByRole("combobox", { name: /move to job/i }));
    fireEvent.click(await screen.findByRole("option", { name: /beta • platform engineer/i }));
    fireEvent.click(screen.getByRole("button", { name: /move thread/i }));

    await waitFor(() => {
      expect(mockedApi.post).toHaveBeenCalledWith("/gmail/relink-thread", expect.objectContaining({
        jobApplicationId: 77,
        threadId: "thread-1",
        removeFromOtherJobs: true,
      }));
    });
  });

  test("shows Gmail sync state diagnostics alongside linked thread continuity", async () => {
    renderDialog();

    fireEvent.click(await screen.findByRole("button", { name: /import email/i }));
    fireEvent.click(await screen.findByRole("tab", { name: /^google$/i }));

    expect(await screen.findByText(/sync checked/i)).toBeInTheDocument();
    expect((await screen.findAllByText(/token refresh failed/i)).length).toBeGreaterThan(0);
  });

  test("manual Gmail search override reloads job candidates with queryOverride", async () => {
    renderDialog();

    fireEvent.click(await screen.findByRole("button", { name: /import email/i }));
    fireEvent.click(await screen.findByRole("tab", { name: /^google$/i }));

    const search = await screen.findByLabelText(/search gmail/i);
    fireEvent.change(search, { target: { value: 'subject:"Acme" newer_than:30d' } });
    fireEvent.click(screen.getByRole("button", { name: /^search$/i }));

    await waitFor(() => {
      expect(mockedApi.get).toHaveBeenCalledWith("/gmail/job-candidates", expect.objectContaining({
        params: expect.objectContaining({
          jobApplicationId: 42,
          queryOverride: 'subject:"Acme" newer_than:30d',
        }),
      }));
    });
  });
});
