import React from "react";
import "@testing-library/jest-dom";
import { fireEvent, render as rtlRender, screen, waitFor } from "@testing-library/react";

import {
  ApplicationAnalysis, ApplicationMatch, ApplicationTimeline,
} from "./components/ApplicationIntelligence";
import { api } from "./api";
import { I18nProvider } from "./i18n/I18nProvider";

const render = (ui: React.ReactElement) => rtlRender(<I18nProvider>{ui}</I18nProvider>);

jest.mock("./api", () => ({
  api: {
    get: jest.fn(),
    interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
  },
  getApiErrorMessage: (_e: any, fallback?: string) => fallback || "Request failed.",
}));

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

const timeline = {
  days: [
    {
      date: "2026-07-19",
      label: "Today",
      events: [
        { id: 2, type: "StatusChanged", category: "stage", summary: "Moved from Applied to Interview", detail: null, isMilestone: true, at: "2026-07-19T09:00:00Z" },
        { id: 3, type: "AiRefreshed", category: "ai", summary: "AI suggestions refreshed", detail: null, isMilestone: false, at: "2026-07-19T08:00:00Z" },
      ],
    },
  ],
  milestones: [
    { id: 2, type: "StatusChanged", category: "stage", summary: "Moved from Applied to Interview", detail: null, isMilestone: true, at: "2026-07-19T09:00:00Z" },
  ],
  categories: ["ai", "stage"],
  totalEvents: 2,
};

const analysis = {
  role: "Senior Backend Developer",
  company: "Acme",
  location: "Oslo",
  employmentType: "Full-time",
  seniority: "Senior",
  salary: null,
  technologies: ["C#", ".NET"],
  skills: ["Collaboration"],
  responsibilities: ["Build and operate REST APIs"],
  keywords: ["C#", ".NET", "Collaboration"],
  summary: "Senior Backend Developer at Acme · Oslo.",
  importantRequirements: ["Strong experience with C# and .NET"],
  interviewTopics: ["C#", ".NET"],
  missingInformation: ["Salary or compensation range"],
  hasJobDescription: true,
  aiSuggestionCount: 0,
};

const match = {
  score: 72,
  band: "Good",
  hasEnoughSignal: true,
  hasCareerProfile: true,
  hasSelectedCv: true,
  selectedCvName: "Backend CV",
  jobLanguage: "en",
  cvLanguage: "en",
  languageMismatch: false,
  usedTranslatedJobDescription: false,
  matchedSkills: ["C#", "SQL"],
  missingSkills: ["Kubernetes"],
  relevantExperience: [{ title: "Backend Developer", subtitle: "Initech · 2021 – present", matched: ["C#"] }],
  relevantProjects: [],
  suggestions: ["Solid match. Lead with the matched skills."],
  aiSuggestionCount: 0,
};

beforeEach(() => jest.clearAllMocks());

// ---------- Timeline ----------

test("timeline renders grouped days, milestones and readable summaries", async () => {
  mockedApi.get.mockResolvedValue({ data: timeline } as any);

  render(<ApplicationTimeline jobId={7} />);

  expect(await screen.findByText("Milestones")).toBeInTheDocument();
  expect(screen.getByText("Today")).toBeInTheDocument();
  expect(screen.getAllByText("Moved from Applied to Interview").length).toBeGreaterThan(0);
  expect(screen.getByText("AI suggestions refreshed")).toBeInTheDocument();
});

test("timeline filters by category", async () => {
  mockedApi.get.mockResolvedValue({ data: timeline } as any);

  render(<ApplicationTimeline jobId={7} />);
  fireEvent.click(await screen.findByRole("button", { name: "AI" }));

  await waitFor(() =>
    expect(mockedApi.get).toHaveBeenLastCalledWith(
      "/jobapplications/7/timeline",
      { params: { category: "ai", milestonesOnly: undefined } },
    ));
});

test("timeline shows an empty state when nothing has happened", async () => {
  mockedApi.get.mockResolvedValue({ data: { days: [], milestones: [], categories: [], totalEvents: 0 } } as any);

  render(<ApplicationTimeline jobId={7} />);

  expect(await screen.findByText(/Nothing has happened yet/i)).toBeInTheDocument();
});

test("timeline surfaces an error instead of rendering nothing", async () => {
  mockedApi.get.mockRejectedValue(new Error("boom"));

  render(<ApplicationTimeline jobId={7} />);

  expect(await screen.findByText(/Could not load this section/i)).toBeInTheDocument();
});

// ---------- Analysis ----------

test("analysis renders the extracted structure", async () => {
  mockedApi.get.mockResolvedValue({ data: analysis } as any);

  render(<ApplicationAnalysis jobId={7} />);

  expect(await screen.findByText("Senior Backend Developer at Acme · Oslo.")).toBeInTheDocument();
  expect(screen.getByText("Full-time")).toBeInTheDocument();
  expect(screen.getByText("Strong experience with C# and .NET")).toBeInTheDocument();
  expect(screen.getByText("Salary or compensation range")).toBeInTheDocument();
});

test("analysis prompts for the advert when there is none", async () => {
  mockedApi.get.mockResolvedValue({
    data: { ...analysis, hasJobDescription: false, technologies: [], responsibilities: [], importantRequirements: [] },
  } as any);

  render(<ApplicationAnalysis jobId={7} />);

  expect(await screen.findByText(/No advert text saved yet/i)).toBeInTheDocument();
});

test("analysis shows a loading state before the data arrives", () => {
  mockedApi.get.mockReturnValue(new Promise(() => {}) as any);

  const { container } = render(<ApplicationAnalysis jobId={7} />);

  expect(container.querySelectorAll(".MuiSkeleton-root").length).toBeGreaterThan(0);
});

// ---------- Match ----------

test("match renders the score, evidence and suggestions", async () => {
  mockedApi.get.mockResolvedValue({ data: match } as any);

  render(<ApplicationMatch jobId={7} />);

  expect(await screen.findByText("72%")).toBeInTheDocument();
  expect(screen.getByText("Good")).toBeInTheDocument();
  expect(screen.getByText("Backend Developer")).toBeInTheDocument();
  expect(screen.getByText("Kubernetes")).toBeInTheDocument();
  expect(screen.getByText(/Solid match/i)).toBeInTheDocument();
});

test("match asks for a career profile before showing a score", async () => {
  mockedApi.get.mockResolvedValue({
    data: {
      ...match, score: 0, band: "No profile", hasCareerProfile: false,
      matchedSkills: [], missingSkills: [], relevantExperience: [],
      suggestions: ["Build your career profile first."],
    },
  } as any);

  render(<ApplicationMatch jobId={7} />);

  expect(await screen.findByText(/linked CV cannot be matched/i)).toBeInTheDocument();
  expect(screen.queryByText("0%")).not.toBeInTheDocument();
});

test("match warns when the advert is too short to score", async () => {
  mockedApi.get.mockResolvedValue({ data: { ...match, hasEnoughSignal: false } } as any);

  render(<ApplicationMatch jobId={7} />);

  expect(await screen.findByText(/too short to score reliably/i)).toBeInTheDocument();
});

test("match explains when a saved translation is used for a cross-language comparison", async () => {
  mockedApi.get.mockResolvedValue({
    data: {
      ...match,
      jobLanguage: "nb",
      cvLanguage: "en",
      languageMismatch: true,
      usedTranslatedJobDescription: true,
    },
  } as any);

  render(<ApplicationMatch jobId={7} />);

  expect(await screen.findByText(/uses the saved English translation/i)).toBeInTheDocument();
});
