import React from "react";
import "@testing-library/jest-dom";
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { createMemoryRouter, RouterProvider, Route, Routes, useLocation } from "react-router-dom";

import { api } from "./api";
import JobTable from "./components/JobTable";
import { ConfirmProvider } from "./confirm";
import { I18nProvider } from "./i18n/I18nProvider";
import { PromptProvider } from "./prompt";
import { ToastProvider } from "./toast";
import ApplicationWorkspacePage from "./views/ApplicationWorkspacePage";

jest.mock("./components/Attachments", () => () => <div>Documents section</div>);
jest.mock("./components/Correspondence", () => () => <div>Communication section</div>);
jest.mock("./components/ApplicationChecklist", () => () => <div>Checklist section</div>);
jest.mock("./components/ApplicationIntelligence", () => ({
  ApplicationAnalysis: () => <div>Analysis section</div>,
  ApplicationMatch: () => <div>Match section</div>,
  ApplicationTimeline: () => <div>Timeline section</div>,
}));
jest.mock("./components/ApplicationAssets", () => ({
  ApplicationCoverLetterSection: ({ onDirtyChange }: { onDirtyChange?: (dirty: boolean) => void }) => (
    <div>
      Cover letter section
      <button onClick={() => onDirtyChange?.(true)}>Make cover letter dirty</button>
    </div>
  ),
  ApplicationCvSection: () => <div>CV section</div>,
  ApplicationPackageDraftsSection: () => <div>Application drafts section</div>,
}));
jest.mock("./components/InterviewPrep", () => ({ ApplicationInterviewPrep: () => <div>Interview section</div> }));
jest.mock("./components/ApplicationWorkflowAssist", () => ({
  ApplicationStatusSuggestion: () => <div>Status suggestion</div>,
  ApplicationStrategySnapshot: () => <div>Strategy section</div>,
}));

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

// React Router's data router builds a Request for in-memory navigations. JSDOM does not provide
// one, and these tests do not run loaders or inspect request bodies, so a small contract stub is
// enough to exercise blocker/history behavior.
class RouterTestRequest {
  url: string;
  method: string;
  signal?: AbortSignal;
  headers: Headers;
  body: unknown;

  constructor(url: string, init: RequestInit = {}) {
    this.url = url;
    this.method = init.method ?? "GET";
    this.signal = init.signal ?? undefined;
    this.headers = new Headers(init.headers);
    this.body = init.body;
  }
}

Object.assign(globalThis, { Request: RouterTestRequest });

const job = {
  id: 42,
  jobTitle: "Backend Developer",
  company: { id: 1, name: "Acme" },
  companyId: 1,
  status: "Waiting",
  dateApplied: "2026-08-01T00:00:00Z",
  savedAt: "2026-08-01T00:00:00Z",
  location: "Oslo",
  description: "Build APIs",
  daysSince: 9,
  isDeleted: false,
  needsFollowUp: false,
  workflowSignal: null,
};

const overview = {
  id: 42,
  jobTitle: "Backend Developer",
  company: "Acme",
  location: "Oslo",
  salary: null,
  status: "Waiting",
  stageGroup: "Applied",
  stageOrder: 1,
  dateApplied: "2026-08-01T00:00:00Z",
  deadline: null,
  followUpAt: null,
  nextAction: null,
  jobUrl: null,
  savedAt: "2026-08-01T00:00:00Z",
  description: "Build APIs",
  translatedDescription: null,
  descriptionLanguage: "en",
  tags: [".NET", "SQL"],
  notes: "Ask about the platform team.",
  applicationAnswerDraft: "Saved answer",
  recruiterMessageDraft: "Saved recruiter message",
  source: "nav",
  countryCode: "NO",
  hasJobDescription: true,
  cv: { variantId: null, variantName: null, themeId: null, hasTailoredCvText: false, updatedAtUtc: null },
  hasCoverLetter: false,
  documentCount: 0,
  hasPortfolio: false,
  aiInteractionCount: 0,
  lastAiAtUtc: null,
  recentActivity: [],
  nextStep: null,
  checklistProgress: { total: 0, completed: 0, dismissed: 0, percent: 0 },
};

function LocationControls() {
  const location = useLocation();
  return (
    <>
      <output data-testid="location">{location.pathname}{location.search}</output>
    </>
  );
}

function renderTable(path = "/jobs") {
  const router = createMemoryRouter([
    {
      path: "*",
      element: (
        <>
          <LocationControls />
          <Routes>
            <Route path="/jobs" element={<JobTable refreshToken={0} pageSize={15} onPageSizeChange={() => {}} columns={{ status: true, dateApplied: true, daysSince: true, jobUrl: false }} onColumnsChange={() => {}} />} />
            <Route path="/jobs/:id" element={<ApplicationWorkspacePage />} />
          </Routes>
        </>
      ),
    },
  ], { initialEntries: [path] });

  return render(
    <ToastProvider>
      <I18nProvider>
        <ConfirmProvider>
          <PromptProvider>
            <RouterProvider router={router} />
          </PromptProvider>
        </ConfirmProvider>
      </I18nProvider>
    </ToastProvider>,
  );
}

beforeEach(() => {
  window.localStorage.removeItem("uiLanguage");
  mockedApi.get.mockImplementation((url: string) => {
    if (url === "/companies") return Promise.resolve({ data: [{ id: 1, name: "Acme" }] } as any);
    if (url === "/jobapplications") return Promise.resolve({ data: { items: [job], total: 1, page: 1, pageSize: 15 } } as any);
    if (url === "/jobapplications/42/workspace") return Promise.resolve({ data: overview } as any);
    if (url === "/jobapplications/999/workspace") return Promise.reject({ response: { status: 404, data: { detail: "Application not found." } } });
    return Promise.resolve({ data: [] } as any);
  });
});

afterEach(() => {
  window.localStorage.removeItem("uiLanguage");
  jest.clearAllMocks();
});

test("opens the dedicated workspace from the whole row and preserves list state on return", async () => {
  renderTable();

  const search = await screen.findByRole("textbox", { name: /search/i });
  fireEvent.change(search, { target: { value: "backend" } });
  fireEvent.click(await screen.findByRole("row", { name: /open backend developer/i }));

  expect(await screen.findByText("Backend Developer")).toBeInTheDocument();
  expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42");

  fireEvent.click(screen.getByRole("tab", { name: "Analysis" }));
  expect(await screen.findByText("Analysis section")).toBeInTheDocument();
  expect(await screen.findByText("Match section")).toBeInTheDocument();
  expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42?section=analysis");

  fireEvent.click(screen.getByRole("button", { name: /back to applications/i }));
  await waitFor(() => expect(screen.getByTestId("location")).toHaveTextContent("/jobs?q=backend"));
  expect(await screen.findByRole("textbox", { name: /search/i })).toHaveValue("backend");
  await waitFor(() => expect(screen.getByRole("row", { name: /open backend developer/i })).toHaveFocus());
});

test("opens a direct workspace URL and returns to applications", async () => {
  renderTable("/jobs/42?section=match");

  expect(await screen.findByText("Analysis section")).toBeInTheDocument();
  expect(await screen.findByText("Match section")).toBeInTheDocument();
  expect(screen.getByRole("tab", { name: "Analysis" })).toHaveAttribute("aria-selected", "true");
  expect(screen.getByLabelText(/application progress: waiting/i)).toBeInTheDocument();

  fireEvent.click(screen.getByRole("button", { name: /back to applications/i }));
  await waitFor(() => expect(screen.getByTestId("location")).toHaveTextContent("/jobs"));
});

test("row controls do not trigger navigation", async () => {
  renderTable();

  const row = await screen.findByRole("row", { name: /open backend developer/i });
  const checkbox = within(row).getByRole("checkbox");
  fireEvent.click(checkbox);

  expect(screen.getByTestId("location")).toHaveTextContent(/^\/jobs$/);
  expect(checkbox).toBeChecked();
});

test("handles a deleted or inaccessible job without rendering a broken workspace", async () => {
  renderTable("/jobs/999");

  expect(await screen.findByRole("alert")).toHaveTextContent("Could not open this application");
  expect(screen.getByRole("button", { name: /back to applications/i })).toBeInTheDocument();
});

test("warns before section navigation would discard application edits", async () => {
  renderTable("/jobs/42?section=cover-letter");

  fireEvent.click(await screen.findByRole("button", { name: "Make cover letter dirty" }));
  fireEvent.click(screen.getByRole("tab", { name: "Analysis" }));

  expect(await screen.findByRole("dialog", { name: /Unsaved application changes/i })).toBeInTheDocument();
  fireEvent.click(screen.getByRole("button", { name: "Keep editing" }));
  expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42?section=cover-letter");
  await waitFor(() => expect(screen.queryByRole("dialog", { name: /Unsaved application changes/i })).not.toBeInTheDocument());

  fireEvent.click(screen.getByRole("tab", { name: "Analysis" }));
  fireEvent.click(await screen.findByRole("button", { name: "Discard and leave" }));
  await waitFor(() => expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42?section=analysis"));
});

test("renders shared workspace navigation and progress in Norwegian Bokmål", async () => {
  window.localStorage.setItem("uiLanguage", "nb");
  mockedApi.get.mockImplementation((url: string) => {
    if (url === "/jobapplications/42/workspace") return Promise.resolve({ data: {
      ...overview,
      nextStep: {
        key: "prepare-cv",
        label: "Prepare a CV for this role",
        reason: "Attach a CV variant tailored to this application.",
        section: "cv",
      },
    } } as any);
    return Promise.resolve({ data: [] } as any);
  });
  renderTable("/jobs/42");

  expect(await screen.findByRole("tab", { name: "Oversikt" })).toHaveAttribute("aria-selected", "true");
  expect(screen.getByRole("tab", { name: "Analyse" })).toBeInTheDocument();
  expect(screen.getByRole("tab", { name: "Søknadsbrev" })).toBeInTheDocument();
  expect(await screen.findByLabelText(/søknadsprogresjon/i)).toBeInTheDocument();
  expect(screen.getByRole("button", { name: /tilbake til søknader/i })).toBeInTheDocument();
  expect(screen.getByRole("heading", { name: "Forbered en CV for denne stillingen" })).toBeInTheDocument();
});

test("hydrates list filters, sort and page from a shareable URL", async () => {
  renderTable("/jobs?q=backend&status=Interview&companyId=1&location=Oslo&needsFollowUp=1&readiness=interview&includeDeleted=1&sortBy=company&sortDir=asc&page=2");

  expect(await screen.findByRole("textbox", { name: /search/i })).toHaveValue("backend");
  await waitFor(() => expect(mockedApi.get).toHaveBeenCalledWith("/jobapplications", {
    params: expect.objectContaining({
      page: 2,
      q: "backend",
      status: "Interview",
      companyId: 1,
      location: "Oslo",
      needsFollowUp: true,
      readiness: "interview",
      includeDeleted: true,
      sortBy: "company",
      sortDir: "asc",
    }),
  }));
});
