import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, waitFor, cleanup, act } from '@testing-library/react';
import {
  AppStateProvider,
  type Draft,
  useDraft,
  useRankSource,
  useKeywords,
  useCompetitorUrls,
} from '../Context.js';
import DraftFetcher from './DraftFetcher.js';
import { server } from '../mocks/server.js';
import { handlers } from '../mocks/handlers.js';

describe('DraftFetcher', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    server.resetHandlers(...handlers);
  });

  afterEach(async () => {
    await act(async () => {
      cleanup();
      await new Promise((resolve) => setTimeout(resolve, 10));
    });
  });

  describe('Draft Fetching', () => {
    let draft: Draft;
    let rankSource: ReturnType<typeof useRankSource>;
    let keywords: string[];
    let competitorUrls: string[];

    test('fetches draft when draft.id exists and lastUpdated is missing', async () => {
      const initialState = {
        accountId: '12345',
        draft: { id: 'bead88f1-bd37-48a7-9861-46f04058141d' },
      };

      const ContextCapture = () => {
        draft = useDraft();
        rankSource = useRankSource();
        keywords = useKeywords();
        competitorUrls = useCompetitorUrls();
        return null;
      };

      render(
        <AppStateProvider initialState={initialState}>
          <DraftFetcher isLoading={() => {}} hasError={() => {}} />
          <ContextCapture />
        </AppStateProvider>,
      );

      await waitFor(() => {
        expect(draft.lastUpdated).toBe(1760026532.0);
      });

      expect(draft.id).toBe('bead88f1-bd37-48a7-9861-46f04058141d');
      expect(draft.title).toBe('Untitled draft: gritty, swoop, phanatic');
      expect(draft.metaDescription).toBe('This is the description');
      expect(draft.bodyCopy).toBe('<p>Hello, world!</p>');
      expect(rankSource.id).toBe(174);
      expect(keywords).toEqual(['gritty', 'swoop', 'phanatic']);
      expect(competitorUrls.length).toBeGreaterThan(0);
    });

    test('does not fetch draft when lastUpdated already exists', async () => {
      const initialState = {
        accountId: '12345',
        draft: {
          id: 'bead88f1-bd37-48a7-9861-46f04058141d',
          lastUpdated: 1760026532,
        },
      };

      const ContextCapture = () => {
        draft = useDraft();
        return null;
      };

      render(
        <AppStateProvider initialState={initialState}>
          <DraftFetcher isLoading={() => {}} hasError={() => {}} />
          <ContextCapture />
        </AppStateProvider>,
      );

      await waitFor(() => {
        expect(draft.lastUpdated).toBe(1760026532);
      });
    });

    test('does not fetch draft when draft.id is missing', async () => {
      const initialState = {
        accountId: '12345',
      };
      const isLoading = vi.fn();
      const hasError = vi.fn();

      render(
        <AppStateProvider initialState={initialState}>
          <DraftFetcher isLoading={isLoading} hasError={hasError} />
        </AppStateProvider>,
      );

      await new Promise((resolve) => setTimeout(resolve, 100));
      expect(hasError).not.toHaveBeenCalledWith(true);
      expect(isLoading).not.toHaveBeenCalledWith(true);
    });

    test('uses fallback title when draftTitle is missing', async () => {
      const initialState = {
        accountId: '12345',
        draft: {
          id: 'bead88f1-bd37-48a7-9861-46f04058141d',
        },
      };

      const ContextCapture = () => {
        draft = useDraft();
        return null;
      };

      render(
        <AppStateProvider initialState={initialState}>
          <DraftFetcher isLoading={() => {}} hasError={() => {}} />
          <ContextCapture />
        </AppStateProvider>,
      );

      await waitFor(() => {
        expect(draft.lastUpdated).toBe(1760026532.0);
      });

      expect(draft.title).toBe(`Untitled Canvas draft`);
    });
  });

  describe('Error Handling', () => {
    let draft: Draft;

    test('calls hasError(false) when fetch succeeds', async () => {
      const hasError = vi.fn();
      const initialState = {
        accountId: '12345',
        draft: { id: 'bead88f1-bd37-48a7-9861-46f04058141d' },
      };

      const ContextCapture = () => {
        draft = useDraft();
        return null;
      };

      render(
        <AppStateProvider initialState={initialState}>
          <DraftFetcher isLoading={() => {}} hasError={hasError} />
          <ContextCapture />
        </AppStateProvider>,
      );

      await waitFor(() => {
        expect(draft.lastUpdated).toBe(1760026532.0);
      });

      expect(hasError).toHaveBeenCalledWith(false);
    });
  });

  describe('Loading State', () => {
    let draft: Draft;

    test('reports loading state while fetching', async () => {
      const isLoading = vi.fn();
      const initialState = {
        accountId: '12345',
        draft: { id: 'bead88f1-bd37-48a7-9861-46f04058141d' },
      };

      const ContextCapture = () => {
        draft = useDraft();
        return null;
      };

      render(
        <AppStateProvider initialState={initialState}>
          <DraftFetcher isLoading={isLoading} hasError={() => {}} />
          <ContextCapture />
        </AppStateProvider>,
      );

      await waitFor(() => {
        expect(draft.lastUpdated).toBe(1760026532.0);
      });

      expect(isLoading).toHaveBeenCalled();
      expect(isLoading).toHaveBeenLastCalledWith(false);
    });

    test('sets loading false when no request is made', async () => {
      const isLoading = vi.fn();
      const initialState = {
        accountId: '12345',
        draft: {
          id: 'bead88f1-bd37-48a7-9861-46f04058141d',
          lastUpdated: 1760026532,
        },
      };

      render(
        <AppStateProvider initialState={initialState}>
          <DraftFetcher isLoading={isLoading} hasError={() => {}} />
        </AppStateProvider>,
      );

      await waitFor(() => {
        expect(isLoading).toHaveBeenCalledWith(false);
      });
    });
  });

  describe('Rank Source', () => {
    let rankSource: ReturnType<typeof useRankSource>;

    test('sets default rank source when no draft.id and no rankSource.id', async () => {
      const initialState = {
        accountId: '12345',
      };

      const ContextCapture = () => {
        rankSource = useRankSource();
        return null;
      };

      render(
        <AppStateProvider initialState={initialState}>
          <DraftFetcher isLoading={() => {}} hasError={() => {}} />
          <ContextCapture />
        </AppStateProvider>,
      );

      await waitFor(() => {
        expect(rankSource.id).toBe(1);
      });

      expect(rankSource.description).toBe('Google (US / English)');
      expect(rankSource.isoLocale).toBe('en_US');
    });

    test('does not set default rank source when rankSource.id exists', async () => {
      const initialState = {
        accountId: '12345',
        rankSource: {
          id: 99,
          description: 'Custom Source',
          isoLocale: 'es_ES',
        },
      };

      const ContextCapture = () => {
        rankSource = useRankSource();
        return null;
      };

      render(
        <AppStateProvider initialState={initialState}>
          <DraftFetcher isLoading={() => {}} hasError={() => {}} />
          <ContextCapture />
        </AppStateProvider>,
      );

      await new Promise((resolve) => setTimeout(resolve, 100));

      expect(rankSource.id).toBe(99);
      expect(rankSource.description).toBe('Custom Source');
    });
  });
});
