import { afterEach, describe, expect, test, vi } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte';
import { mockQuery } from '../../../test-utils/query';
import type { LidarrConfig } from '$lib/api/types';

// useQueryClient is wrapped so the page can call invalidateQueries() without
// a real QueryClient context. Everything else from svelte-query passes through.
vi.mock('@tanstack/svelte-query', async (orig) => {
  const actual = (await orig()) as Record<string, unknown>;
  return { ...actual, useQueryClient: () => ({ invalidateQueries: vi.fn() }) };
});

vi.mock('$lib/api/admin', () => ({
  createLidarrConfigQuery: vi.fn(),
  createQualityProfilesQuery: vi.fn(),
  createMetadataProfilesQuery: vi.fn(),
  createRootFoldersQuery: vi.fn(),
  putLidarrConfig: vi.fn(),
  testLidarrConnection: vi.fn()
}));

import IntegrationsPage from './+page.svelte';
import {
  createLidarrConfigQuery,
  createQualityProfilesQuery,
  createMetadataProfilesQuery,
  createRootFoldersQuery,
  putLidarrConfig,
  testLidarrConnection
} from '$lib/api/admin';

const cfgConnected: LidarrConfig = {
  enabled: true,
  base_url: 'http://lidarr.local',
  api_key: '***',
  default_quality_profile_id: 1,
  default_metadata_profile_id: 1,
  default_root_folder_path: '/music'
};

const cfgUnset: LidarrConfig = {
  enabled: false,
  base_url: '',
  api_key: '',
  default_quality_profile_id: 0,
  default_metadata_profile_id: 0,
  default_root_folder_path: ''
};

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

function setup(opts: { config?: LidarrConfig } = {}) {
  (createLidarrConfigQuery as ReturnType<typeof vi.fn>).mockReturnValue(
    mockQuery({ data: opts.config ?? cfgConnected })
  );
  (createQualityProfilesQuery as ReturnType<typeof vi.fn>).mockReturnValue(
    mockQuery({
      data: [
        { id: 1, name: 'Standard' },
        { id: 2, name: 'Lossless' }
      ]
    })
  );
  (createMetadataProfilesQuery as ReturnType<typeof vi.fn>).mockReturnValue(
    mockQuery({
      data: [
        { id: 1, name: 'Standard' },
        { id: 2, name: 'Singles only' }
      ]
    })
  );
  (createRootFoldersQuery as ReturnType<typeof vi.fn>).mockReturnValue(
    mockQuery({ data: [{ path: '/music', accessible: true, free_space: 0 }] })
  );
  return render(IntegrationsPage);
}

// Helper for finding the modal's Disconnect confirm button. Both the panel
// trigger and the modal confirm share the visible name "Disconnect"; the
// modal one is the descendant of role="dialog".
function modalConfirmButton(): HTMLButtonElement {
  const matches = screen.getAllByRole('button', { name: /^disconnect$/i });
  const inDialog = matches.find((b) => b.closest('[role="dialog"]') !== null);
  if (!inDialog) throw new Error('modal Disconnect button not found');
  return inDialog as HTMLButtonElement;
}

describe('/admin/integrations', () => {
  test('Save calls putLidarrConfig with empty api_key when input is blank', async () => {
    setup();
    (putLidarrConfig as ReturnType<typeof vi.fn>).mockResolvedValueOnce(cfgConnected);
    await fireEvent.click(screen.getByRole('button', { name: /save changes/i }));
    expect(putLidarrConfig).toHaveBeenCalledWith(
      expect.objectContaining({
        enabled: true,
        base_url: 'http://lidarr.local',
        api_key: '',
        default_quality_profile_id: 1,
        default_root_folder_path: '/music'
      })
    );
  });

  test('Save with new api_key sends the typed value', async () => {
    setup();
    const apiInput = screen.getByPlaceholderText(/saved — leave empty to keep/i);
    await fireEvent.input(apiInput, { target: { value: 'newkey' } });
    (putLidarrConfig as ReturnType<typeof vi.fn>).mockResolvedValueOnce(cfgConnected);
    await fireEvent.click(screen.getByRole('button', { name: /save changes/i }));
    expect(putLidarrConfig).toHaveBeenCalledWith(
      expect.objectContaining({ api_key: 'newkey' })
    );
  });

  test('Test connection renders Lidarr version on success', async () => {
    setup();
    (testLidarrConnection as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
      ok: true,
      version: '2.0.0'
    });
    await fireEvent.click(
      screen.getByRole('button', { name: /test connection/i })
    );
    await waitFor(() =>
      expect(screen.getByText(/lidarr 2\.0\.0/i)).toBeInTheDocument()
    );
  });

  test('Test connection renders error code on failure', async () => {
    setup();
    (testLidarrConnection as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
      ok: false,
      error: 'lidarr_unreachable'
    });
    await fireEvent.click(
      screen.getByRole('button', { name: /test connection/i })
    );
    await waitFor(() =>
      expect(screen.getByText(/lidarr_unreachable/i)).toBeInTheDocument()
    );
  });

  test('Disconnect requires typed confirm; cancelling does not call API', async () => {
    setup();
    // Open modal via the panel-level Disconnect button.
    await fireEvent.click(
      screen.getByRole('button', { name: /^disconnect$/i })
    );
    expect(screen.getByRole('dialog')).toBeInTheDocument();
    // Confirm button is disabled until "DISCONNECT" is typed.
    expect(modalConfirmButton().disabled).toBe(true);
    await fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
    expect(putLidarrConfig).not.toHaveBeenCalled();
    expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
  });

  test('Disconnect with typed confirmation calls API with cleared config', async () => {
    setup();
    await fireEvent.click(
      screen.getByRole('button', { name: /^disconnect$/i })
    );
    const confirmInput = screen.getByPlaceholderText('DISCONNECT');
    await fireEvent.input(confirmInput, { target: { value: 'DISCONNECT' } });
    (putLidarrConfig as ReturnType<typeof vi.fn>).mockResolvedValueOnce(cfgUnset);
    await fireEvent.click(modalConfirmButton());
    expect(putLidarrConfig).toHaveBeenCalledWith(
      expect.objectContaining({
        enabled: false,
        base_url: '',
        api_key: '',
        default_quality_profile_id: 0,
        default_root_folder_path: ''
      })
    );
  });

  test('Quality / metadata / root folder dropdowns populate from API', () => {
    setup();
    // 'Standard' appears in both quality + metadata fixtures, so use *AllBy*.
    expect(screen.getAllByText('Standard').length).toBeGreaterThanOrEqual(2);
    expect(screen.getByText('Lossless')).toBeInTheDocument();
    expect(screen.getByText('Singles only')).toBeInTheDocument();
    expect(screen.getByText('/music')).toBeInTheDocument();
  });

  test('header shows "Lidarr · connected" when configured', () => {
    setup();
    expect(screen.getByText(/lidarr · connected/i)).toBeInTheDocument();
  });

  test('header shows "Lidarr · unset" when not configured', () => {
    setup({ config: cfgUnset });
    expect(screen.getByText(/lidarr · unset/i)).toBeInTheDocument();
  });
});
