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 JobDetailsDialog from './components/JobDetailsDialog';
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>
            <JobDetailsDialog open jobId={42} onClose={() => {}} />
          </PromptProvider>
        </ConfirmProvider>
      </I18nProvider>
    </ToastProvider>,
  );
}

beforeEach(() => {
  Object.assign(navigator, {
    clipboard: {
      writeText: jest.fn().mockResolvedValue(undefined),
    },
  });
  Object.assign(URL, {
    createObjectURL: jest.fn().mockReturnValue('blob:preview-pdf'),
    revokeObjectURL: jest.fn(),
  });

  mockedApi.get.mockImplementation((url: string) => {
    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: 'recruiter@acme.test' },
        tailoredCvText: 'Saved CV',
        coverLetterText: 'Saved cover letter',
        recruiterMessageDraft: 'Saved recruiter message',
        notes: 'Original notes\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nSaved application answer\n<<<END_APPLICATION_ANSWER_DRAFT>>>',
        shortSummary: 'summary'
      } } as any);
    }
    if (url === '/auth/me') {
      return Promise.resolve({ data: { roles: [], avatarImageDataUrl: 'data:image/png;base64,avatar123' } } as any);
    }
    if (url === '/jobapplications/42/history') {
      return Promise.resolve({ data: [] } as any);
    }
    if (url === '/attachments/42') {
      return Promise.resolve({ data: [{ id: 9, fileName: 'resume.pdf', uploadDate: new Date().toISOString(), fileType: 'application/pdf', fileSize: 1234, purpose: 'resume', useForAi: true }] } as any);
    }
    if (url === '/jobapplications/42/tailored-cv-draft') {
      return Promise.resolve({ data: {
        id: 5,
        canonicalProfileVersion: 3,
        templateId: 'ats-minimal',
        headline: 'Backend Engineer',
        summary: ['Built APIs', 'Shipped backend work'],
        selectedSkills: ['.NET', 'SQL'],
        experience: [],
        education: [],
        customSections: [],
        renderOptions: { showPhoto: false, pageMode: 'one-page', accentColor: 'slate', sectionOrder: ['summary', 'skills', 'experience', 'education', 'custom'], bulletDensity: 'balanced' },
        generationContextHash: 'abc123',
        lastGeneratedAtUtc: new Date().toISOString(),
        lastEditedAtUtc: null,
        status: 'generated',
        renderedText: 'Backend Engineer\n\nProfessional Summary\n- Built APIs\n- Shipped backend work',
        isLegacyFallback: false,
      } } as any);
    }
    if (url === '/jobapplications/42/candidate-fit') {
      return Promise.resolve({ data: { matchSummary: 'Strong fit summary', fitLevel: 'Strong match', matchScore: 84, strengths: ['.NET'], gaps: ['Kubernetes'], mention: [], avoid: [], cvImprovements: [], missingKeywords: [], interviewPrep: [], tailoredPitch: 'Pitch', guidance: { cv: [], coverLetter: [], interview: [], recruiterMessage: [] } } } as any);
    }
    if (url === '/jobapplications/42/focus-plan') {
      return Promise.resolve({ data: { strategicSummary: 'Lead with backend delivery and measurable outcomes.', immediatePriorities: ['Highlight .NET ownership'], cvBulletIdeas: [], proofPointsToLeadWith: [], coverLetterAngles: [], followUpApproach: [] } } as any);
    }
    if (url === '/jobapplications/42/focus-plan/operation') {
      return Promise.reject({ response: { status: 404 } });
    }
    return Promise.resolve({ data: [] } as any);
  });

  mockedApi.post.mockImplementation((url: string, body?: any, config?: any) => {
    if (url === '/jobapplications/42/generate-tailored-cv-draft') {
      return Promise.resolve({ data: {
        id: 5,
        canonicalProfileVersion: 3,
        templateId: 'ats-minimal',
        headline: 'Senior Backend Engineer',
        summary: ['Owned API delivery', 'Improved SQL workflows'],
        selectedSkills: ['.NET', 'SQL', 'APIs'],
        experience: [],
        education: [],
        customSections: [],
        renderOptions: { showPhoto: false, pageMode: 'one-page', accentColor: 'slate', sectionOrder: ['summary', 'skills', 'experience', 'education', 'custom'], bulletDensity: 'balanced' },
        generationContextHash: 'def456',
        lastGeneratedAtUtc: new Date().toISOString(),
        lastEditedAtUtc: null,
        status: 'generated',
        renderedText: 'Senior Backend Engineer\n\nProfessional Summary\n- Owned API delivery\n- Improved SQL workflows',
        isLegacyFallback: false,
      } } as any);
    }
    if (url === '/jobapplications/42/tailored-cv-preview') {
      return Promise.resolve({ data: {
        templateId: body?.templateId ?? 'ats-minimal',
        suggestedFileName: `${body?.templateId ?? 'ats-minimal'}.pdf`,
        html: `<html><body data-template="${body?.templateId ?? 'ats-minimal'}" data-accent="${body?.renderOptions?.accentColor ?? ''}" data-photo="${body?.useProfileAvatar ? 'profile' : 'custom'}"></body></html>`,
      } } as any);
    }
    if (url === '/jobapplications/42/export-tailored-cv-pdf') {
      return Promise.resolve({ data: new Blob(['pdf'], { type: 'application/pdf' }) } as any);
    }
    if (url === '/jobapplications/42/generate-application-package') {
      return Promise.resolve({ data: { tailoredCvText: 'Generated package CV', coverLetterDraft: 'Draft letter', applicationAnswerDraft: 'Draft answer', recruiterMessageDraft: 'Recruiter hello', keyPoints: ['Lead with .NET'], attachmentSignals: [], attachmentFilesUsed: [], coverLetterVariants: ['Variant A'], recruiterMessageVariants: ['Variant B'] } } as any);
    }
    if (url === '/jobapplications/42/focus-plan/operations') {
      return Promise.resolve({ data: { created: true, statusUrl: '/api/operations/strategy-1', operation: {
        id: 'strategy-1', taskType: 'strategy.snapshot', status: 'succeeded', subjectType: 'job_strategy', createdAtUtc: new Date().toISOString(), canCancel: false, canRetry: false,
      } } } as any);
    }
    return Promise.resolve({ data: {} } as any);
  });

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

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

test('tailored cv tab loads, regenerates, and saves the structured tailored draft', async () => {
  renderDialog();

  fireEvent.click(await screen.findByRole('tab', { name: /tailored cv/i }));

  expect(await screen.findByDisplayValue('Backend Engineer')).toBeInTheDocument();
  expect((await screen.findByLabelText('Summary bullets')) as HTMLInputElement).toHaveValue('Built APIs\nShipped backend work');

  fireEvent.click(screen.getByRole('button', { name: /generate tailored draft/i }));

  expect(await screen.findByDisplayValue('Senior Backend Engineer')).toBeInTheDocument();
  const headline = screen.getByDisplayValue('Senior Backend Engineer');
  fireEvent.change(headline, { target: { value: 'Principal Backend Engineer' } });

  fireEvent.click(screen.getByRole('button', { name: /save tailored draft/i }));

  await waitFor(() => {
    expect(mockedApi.put).toHaveBeenCalledWith('/jobapplications/42/tailored-cv-draft', expect.objectContaining({
      headline: 'Principal Backend Engineer',
      summary: ['Owned API delivery', 'Improved SQL workflows'],
      selectedSkills: ['.NET', 'SQL', 'APIs'],
      status: 'edited',
    }));
  });

  expect(mockedApi.put).not.toHaveBeenCalledWith('/jobapplications/42/tailored-cv', expect.anything());
});

test('application package drafts save separately from the tailored cv draft', async () => {
  renderDialog();

  fireEvent.click(await screen.findByRole('tab', { name: /tailored cv/i }));
  fireEvent.click(await screen.findByRole('button', { name: /generate application package/i }));

  const coverLetter = await screen.findByDisplayValue('Draft letter');
  const applicationAnswer = await screen.findByDisplayValue('Draft answer');
  const recruiterMessage = await screen.findByDisplayValue('Recruiter hello');

  fireEvent.change(coverLetter, { target: { value: 'Edited cover letter' } });
  fireEvent.change(applicationAnswer, { target: { value: 'Edited answer' } });
  fireEvent.change(recruiterMessage, { target: { value: 'Edited recruiter note' } });

  fireEvent.click(screen.getByRole('button', { name: /save package drafts/i }));

  await waitFor(() => {
    expect(mockedApi.put).toHaveBeenCalledWith('/jobapplications/42/application-drafts', {
      coverLetterText: 'Edited cover letter',
      notes: 'Original notes\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nEdited answer\n<<<END_APPLICATION_ANSWER_DRAFT>>>',
      recruiterMessageDraft: 'Edited recruiter note',
    });
  });
});

test('template switching refreshes preview and export uses the selected template payload', async () => {
  renderDialog();

  fireEvent.click(await screen.findByRole('tab', { name: /tailored cv/i }));

  const comboboxes = await screen.findAllByRole('combobox');
  fireEvent.mouseDown(comboboxes[1]);
  fireEvent.click(await screen.findByRole('option', { name: 'Harvard' }));

  const accent = screen.getByLabelText('Accent');
  fireEvent.change(accent, { target: { value: '#123456' } });

  fireEvent.click(screen.getByRole('button', { name: /preview pdf layout/i }));

  await waitFor(() => {
    expect(mockedApi.post).toHaveBeenCalledWith('/jobapplications/42/tailored-cv-preview', expect.objectContaining({
      templateId: 'harvard',
      renderOptions: expect.objectContaining({ accentColor: '#123456' }),
      useProfileAvatar: true,
    }));
  });

  expect(await screen.findByTitle('Tailored CV preview')).toHaveAttribute('sandbox', 'allow-same-origin');

  const appendChildSpy = jest.spyOn(document.body, 'appendChild');
  const removeSpy = jest.spyOn(HTMLAnchorElement.prototype, 'remove').mockImplementation(() => {});
  const clickSpy = jest.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {});

  fireEvent.click(screen.getByRole('button', { name: /download pdf/i }));

  await waitFor(() => {
    expect(mockedApi.post).toHaveBeenCalledWith('/jobapplications/42/export-tailored-cv-pdf', expect.objectContaining({
      templateId: 'harvard',
      renderOptions: expect.objectContaining({ accentColor: '#123456' }),
    }), expect.objectContaining({ responseType: 'blob' }));
    expect(URL.createObjectURL).toHaveBeenCalled();
    expect(clickSpy).toHaveBeenCalled();
  });

  appendChildSpy.mockRestore();
  removeSpy.mockRestore();
  clickSpy.mockRestore();
});

test('strategy snapshot can be generated from overview', async () => {
  renderDialog();

  expect(await screen.findByRole('button', { name: /generate strategy snapshot/i })).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: /generate strategy snapshot/i }));

  expect(await screen.findByText(/lead with backend delivery and measurable outcomes/i)).toBeInTheDocument();
  expect(await screen.findByText(/highlight \.net ownership/i)).toBeInTheDocument();
});

test('strategy snapshot exposes queued and cancelled durable states', async () => {
  mockedApi.post.mockImplementation((url: string) => {
    if (url === '/jobapplications/42/focus-plan/operations') {
      return Promise.resolve({ data: { created: true, statusUrl: '/api/operations/strategy-queued', operation: {
        id: 'strategy-queued', taskType: 'strategy.snapshot', status: 'queued', subjectType: 'job_strategy', createdAtUtc: new Date().toISOString(), canCancel: true, canRetry: false,
      } } } as any);
    }
    if (url === '/operations/strategy-queued/cancel') {
      return Promise.resolve({ data: {
        id: 'strategy-queued', taskType: 'strategy.snapshot', status: 'cancelled', subjectType: 'job_strategy', createdAtUtc: new Date().toISOString(), canCancel: false, canRetry: true,
      } } as any);
    }
    return Promise.resolve({ data: {} } as any);
  });
  renderDialog();

  fireEvent.click(await screen.findByRole('button', { name: /generate strategy snapshot/i }));
  expect(await screen.findByText(/strategy snapshot: queued/i)).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
  expect(await screen.findByText(/strategy snapshot: cancelled/i)).toBeInTheDocument();
});

test('failed strategy snapshot offers a safe retry', async () => {
  mockedApi.post.mockImplementation((url: string) => {
    if (url === '/jobapplications/42/focus-plan/operations') {
      return Promise.resolve({ data: { created: false, statusUrl: '/api/operations/strategy-failed', operation: {
        id: 'strategy-failed', taskType: 'strategy.snapshot', status: 'failed', subjectType: 'job_strategy', createdAtUtc: new Date().toISOString(), failureCategory: 'provider_unavailable', canCancel: false, canRetry: true,
      } } } as any);
    }
    if (url === '/operations/strategy-failed/retry') {
      return Promise.resolve({ data: {
        id: 'strategy-failed', taskType: 'strategy.snapshot', status: 'queued', subjectType: 'job_strategy', createdAtUtc: new Date().toISOString(), canCancel: true, canRetry: false,
      } } as any);
    }
    return Promise.resolve({ data: {} } as any);
  });
  renderDialog();

  fireEvent.click(await screen.findByRole('button', { name: /generate strategy snapshot/i }));
  expect(await screen.findByText(/strategy snapshot: failed.*provider unavailable/i)).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: /retry/i }));
  expect(await screen.findByText(/strategy snapshot: queued/i)).toBeInTheDocument();
});
