import { describe, it, expect, vi, afterEach } from 'vitest';
import { screen, waitFor, fireEvent } from '@solidjs/testing-library';
import { renderWithProviders } from '../test-utils';
import { DEFAULT_SETTINGS } from '~/types';
import type { UserSettings, ProviderConfig } from '~/types';

// Mock API modules
vi.mock('~/api/settings', () => ({
  settingsApi: {
    get: vi.fn(),
    update: vi.fn(),
  },
}));

vi.mock('~/api/config', () => ({
  configApi: {
    listProviders: vi.fn(),
  },
}));

vi.mock('~/api/apiKeys', () => ({
  apiKeysApi: {
    list: vi.fn(),
    create: vi.fn(),
    remove: vi.fn(),
    test: vi.fn(),
    exportKeys: vi.fn(),
  },
}));

import { settingsApi } from '~/api/settings';
import { configApi } from '~/api/config';
import { apiKeysApi } from '~/api/apiKeys';
import Settings from '~/pages/Settings';

const mockedGetSettings = vi.mocked(settingsApi.get);
const mockedUpdateSettings = vi.mocked(settingsApi.update);
const mockedListProviders = vi.mocked(configApi.listProviders);
const mockedListKeys = vi.mocked(apiKeysApi.list);

const mockSettings: UserSettings = {
  ...DEFAULT_SETTINGS,
  theme: 'Intelligence Artificielle',
  ai_provider: 'gemini',
  ai_model: 'gemini-2.5-pro',
  ai_model_writing: 'gemini-2.5-flash',
  rate_limit_max_requests: null,
  rate_limit_time_window_seconds: null,
};

const mockProviders: ProviderConfig[] = [
  {
    provider_name: 'gemini',
    display_name: 'Google Gemini',
    models: [
      { model_id: 'gemini-2.5-pro', display_name: 'Gemini 2.5 Pro' },
      { model_id: 'gemini-2.5-flash', display_name: 'Gemini 2.5 Flash' },
    ],
  },
  {
    provider_name: 'openai',
    display_name: 'OpenAI',
    models: [
      { model_id: 'gpt-4o', display_name: 'GPT-4o' },
      { model_id: 'gpt-4o-mini', display_name: 'GPT-4o Mini' },
    ],
  },
];

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

describe('Settings Page', () => {
  it('should render form fields populated from API response', async () => {
    mockedGetSettings.mockResolvedValue(mockSettings);
    mockedListProviders.mockResolvedValue(mockProviders);
    mockedListKeys.mockResolvedValue([]);

    renderWithProviders(() => <Settings />);

    await waitFor(() => {
      const themeInput = screen.getByLabelText(
        'Theme de la recherche',
      ) as HTMLInputElement;
      expect(themeInput.value).toBe('Intelligence Artificielle');
    });
  });

  it('should call PUT /settings when save button is clicked', async () => {
    mockedGetSettings.mockResolvedValue(mockSettings);
    mockedListProviders.mockResolvedValue(mockProviders);
    mockedListKeys.mockResolvedValue([]);
    mockedUpdateSettings.mockResolvedValue(mockSettings);

    renderWithProviders(() => <Settings />);

    await waitFor(() => {
      expect(
        screen.getByLabelText('Theme de la recherche'),
      ).toBeInTheDocument();
    });

    const saveButton = screen.getByText('Enregistrer les parametres');
    fireEvent.click(saveButton);

    await waitFor(() => {
      expect(mockedUpdateSettings).toHaveBeenCalled();
    });
  });

  it('should populate provider dropdown from config API', async () => {
    mockedGetSettings.mockResolvedValue(mockSettings);
    mockedListProviders.mockResolvedValue(mockProviders);
    mockedListKeys.mockResolvedValue([]);

    renderWithProviders(() => <Settings />);

    // The provider names appear in the dropdown (<option>) AND in the ApiKeyManager.
    // Use getAllByText and check that at least one match exists.
    await waitFor(() => {
      expect(screen.getAllByText('Google Gemini').length).toBeGreaterThanOrEqual(1);
    });
    expect(screen.getAllByText('OpenAI').length).toBeGreaterThanOrEqual(1);

    // Verify the provider select has the options
    const providerSelect = screen.getByLabelText("Fournisseur d'IA") as HTMLSelectElement;
    const options = providerSelect.querySelectorAll('option');
    const optionTexts = Array.from(options).map((o) => o.textContent);
    expect(optionTexts).toContain('Google Gemini');
    expect(optionTexts).toContain('OpenAI');
  });

  it('should update model list when selecting a different provider', async () => {
    mockedGetSettings.mockResolvedValue(mockSettings);
    mockedListProviders.mockResolvedValue(mockProviders);
    mockedListKeys.mockResolvedValue([]);

    renderWithProviders(() => <Settings />);

    await waitFor(() => {
      expect(screen.getAllByText('Google Gemini').length).toBeGreaterThanOrEqual(1);
    });

    // Initially Gemini is selected, so its models should be visible in the research model dropdown
    const researchSelect = screen.getByLabelText("Modele d'IA (Recherche et Extraction)") as HTMLSelectElement;
    const geminiOptions = researchSelect.querySelectorAll('option');
    const geminiTexts = Array.from(geminiOptions).map((o) => o.textContent);
    expect(geminiTexts).toContain('Gemini 2.5 Pro');

    // Change provider to OpenAI
    const providerSelect = screen.getByLabelText(
      "Fournisseur d'IA",
    ) as HTMLSelectElement;
    fireEvent.change(providerSelect, { target: { value: 'openai' } });

    await waitFor(() => {
      const updatedOptions = researchSelect.querySelectorAll('option');
      const updatedTexts = Array.from(updatedOptions).map((o) => o.textContent);
      expect(updatedTexts).toContain('GPT-4o');
    });
  });

  it('should render two model dropdowns (research + writing)', async () => {
    mockedGetSettings.mockResolvedValue(mockSettings);
    mockedListProviders.mockResolvedValue(mockProviders);
    mockedListKeys.mockResolvedValue([]);

    renderWithProviders(() => <Settings />);

    await waitFor(() => {
      expect(
        screen.getByLabelText("Modele d'IA (Recherche et Extraction)"),
      ).toBeInTheDocument();
    });
    expect(
      screen.getByLabelText("Modele d'IA (Redaction et Synthese)"),
    ).toBeInTheDocument();
  });

  it('should render rate limit inputs (empty when null)', async () => {
    mockedGetSettings.mockResolvedValue(mockSettings);
    mockedListProviders.mockResolvedValue(mockProviders);
    mockedListKeys.mockResolvedValue([]);

    renderWithProviders(() => <Settings />);

    await waitFor(() => {
      const maxReqInput = screen.getByLabelText(
        'Requetes maximum',
      ) as HTMLInputElement;
      expect(maxReqInput.value).toBe('');
    });

    const timeWindowInput = screen.getByLabelText(
      'Fenetre de temps (secondes)',
    ) as HTMLInputElement;
    expect(timeWindowInput.value).toBe('');
  });

  it('should show and clear rate limit values with the reset link', async () => {
    const settingsWithRateLimits: UserSettings = {
      ...mockSettings,
      rate_limit_max_requests: 10,
      rate_limit_time_window_seconds: 60,
    };
    mockedGetSettings.mockResolvedValue(settingsWithRateLimits);
    mockedListProviders.mockResolvedValue(mockProviders);
    mockedListKeys.mockResolvedValue([]);

    renderWithProviders(() => <Settings />);

    await waitFor(() => {
      const maxReqInput = screen.getByLabelText(
        'Requetes maximum',
      ) as HTMLInputElement;
      expect(maxReqInput.value).toBe('10');
    });

    const resetLink = screen.getByText('Reinitialiser');
    fireEvent.click(resetLink);

    await waitFor(() => {
      const maxReqInput = screen.getByLabelText(
        'Requetes maximum',
      ) as HTMLInputElement;
      expect(maxReqInput.value).toBe('');
    });
  });

  it('should trigger download when export button is clicked', async () => {
    mockedGetSettings.mockResolvedValue(mockSettings);
    mockedListProviders.mockResolvedValue(mockProviders);
    mockedListKeys.mockResolvedValue([]);

    // Mock URL.createObjectURL and revokeObjectURL
    const mockCreateObjectURL = vi.fn(() => 'blob:test');
    const mockRevokeObjectURL = vi.fn();
    const origCreateObjectURL = URL.createObjectURL;
    const origRevokeObjectURL = URL.revokeObjectURL;
    URL.createObjectURL = mockCreateObjectURL;
    URL.revokeObjectURL = mockRevokeObjectURL;

    renderWithProviders(() => <Settings />);

    await waitFor(() => {
      expect(screen.getByTitle('Exporter')).toBeInTheDocument();
    });

    const exportButton = screen.getByTitle('Exporter');
    fireEvent.click(exportButton);

    await waitFor(() => {
      expect(mockCreateObjectURL).toHaveBeenCalled();
    });

    // Restore
    URL.createObjectURL = origCreateObjectURL;
    URL.revokeObjectURL = origRevokeObjectURL;
  });

  it('should populate form from imported JSON file', async () => {
    mockedGetSettings.mockResolvedValue(mockSettings);
    mockedListProviders.mockResolvedValue(mockProviders);
    mockedListKeys.mockResolvedValue([]);

    renderWithProviders(() => <Settings />);

    await waitFor(() => {
      expect(screen.getByTitle('Importer')).toBeInTheDocument();
    });

    const importedSettings = {
      ...DEFAULT_SETTINGS,
      theme: 'Cybersecurite',
      categories: ['Cat A', 'Cat B'],
    };

    const file = new File(
      [JSON.stringify(importedSettings)],
      'settings.json',
      { type: 'application/json' },
    );

    // Find the hidden file input
    const fileInput = document.querySelector(
      'input[type="file"][accept=".json"]',
    ) as HTMLInputElement;
    expect(fileInput).toBeTruthy();

    // Simulate file selection
    Object.defineProperty(fileInput, 'files', {
      value: [file],
      writable: false,
    });
    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(
        screen.getByText("Configuration importee avec succes. N'oubliez pas d'enregistrer."),
      ).toBeInTheDocument();
    });
  });

  it('should show validation error for empty theme', async () => {
    mockedGetSettings.mockResolvedValue(mockSettings);
    mockedListProviders.mockResolvedValue(mockProviders);
    mockedListKeys.mockResolvedValue([]);
    // Simulate backend returning a validation error
    mockedUpdateSettings.mockRejectedValue({
      status: 422,
      message: 'Theme is required',
    });

    renderWithProviders(() => <Settings />);

    await waitFor(() => {
      expect(
        screen.getByLabelText('Theme de la recherche'),
      ).toBeInTheDocument();
    });

    // Clear the theme field
    const themeInput = screen.getByLabelText(
      'Theme de la recherche',
    ) as HTMLInputElement;
    fireEvent.input(themeInput, { target: { value: '' } });

    // Click save
    const saveButton = screen.getByText('Enregistrer les parametres');
    fireEvent.click(saveButton);

    await waitFor(() => {
      expect(screen.getByText('Theme is required')).toBeInTheDocument();
    });
  });
});
