import { describe, it, expect, vi, beforeEach } from 'vitest';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import React from 'react';

const STATE_THREE_UNSET = {
  active: null,
  sources: [
    { source: 'app', set: false, masked: null, whoami_user: null, whoami_ok: false },
    { source: 'env', set: false, masked: null, whoami_user: null, whoami_ok: false },
    { source: 'hf-cli', set: false, masked: null, whoami_user: null, whoami_ok: false },
  ],
};

const STATE_APP_ACTIVE = {
  active: 'app',
  sources: [
    { source: 'app', set: true, masked: 'hf_…abc', whoami_user: 'alice', whoami_ok: true },
    { source: 'env', set: false, masked: null, whoami_user: null, whoami_ok: false },
    { source: 'hf-cli', set: false, masked: null, whoami_user: null, whoami_ok: false },
  ],
};

const STATE_ENV_ACTIVE = {
  active: 'env',
  sources: [
    { source: 'app', set: false, masked: null, whoami_user: null, whoami_ok: false },
    { source: 'env', set: true, masked: 'hf_…xyz', whoami_user: 'bob', whoami_ok: true },
    { source: 'hf-cli', set: false, masked: null, whoami_user: null, whoami_ok: false },
  ],
};

function mockFetchOnce(payload, status = 200) {
  return vi.fn().mockResolvedValueOnce({
    ok: status >= 200 && status < 300,
    status,
    json: async () => payload,
    text: async () => JSON.stringify(payload),
  });
}

function mockFetchSequence(...responses) {
  const fn = vi.fn();
  for (const r of responses) {
    fn.mockResolvedValueOnce({
      ok: r.status >= 200 && r.status < 300,
      status: r.status,
      json: async () => r.body,
      text: async () => JSON.stringify(r.body),
    });
  }
  return fn;
}

import ApiKeysPanel from './ApiKeysPanel';

describe('ApiKeysPanel', () => {
  beforeEach(() => {
    vi.restoreAllMocks();
  });

  it('renders 3 source rows after mount', async () => {
    global.fetch = mockFetchOnce(STATE_THREE_UNSET);
    const { container } = render(<ApiKeysPanel />);
    await waitFor(() => {
      const rows = container.querySelectorAll('.apikeys-row');
      expect(rows.length).toBe(3);
      expect(container.querySelector('[data-source="app"]')).not.toBeNull();
      expect(container.querySelector('[data-source="env"]')).not.toBeNull();
      expect(container.querySelector('[data-source="hf-cli"]')).not.toBeNull();
    });
  });

  it('shows the Active badge on the row matching state.active', async () => {
    global.fetch = mockFetchOnce(STATE_APP_ACTIVE);
    const { container } = render(<ApiKeysPanel />);
    await waitFor(() => {
      const appRow = container.querySelector('[data-source="app"]');
      expect(appRow).not.toBeNull();
      expect(appRow.classList.contains('apikeys-row--active')).toBe(true);
      const badge = appRow.querySelector('.apikeys-badge--active');
      expect(badge?.textContent).toMatch(/active/i);
    });
  });

  it('moves the Active badge when the env source is active', async () => {
    global.fetch = mockFetchOnce(STATE_ENV_ACTIVE);
    const { container } = render(<ApiKeysPanel />);
    await waitFor(() => {
      const envRow = container.querySelector('[data-source="env"]');
      expect(envRow?.classList.contains('apikeys-row--active')).toBe(true);
      const appRow = container.querySelector('[data-source="app"]');
      expect(appRow?.classList.contains('apikeys-row--active')).toBe(false);
    });
  });

  it('Save button POSTs the entered token and refetches state', async () => {
    const fetchMock = mockFetchSequence(
      { status: 200, body: STATE_THREE_UNSET },        // initial GET
      { status: 200, body: STATE_APP_ACTIVE },         // POST returns updated state
      { status: 200, body: STATE_APP_ACTIVE },         // GET after save
    );
    global.fetch = fetchMock;

    render(<ApiKeysPanel />);
    await waitFor(() => screen.getByPlaceholderText(/hf_/));

    const input = screen.getByPlaceholderText(/hf_/);
    fireEvent.change(input, { target: { value: 'hf_newtoken123' } });
    const saveBtn = screen.getByRole('button', { name: /save/i });
    fireEvent.click(saveBtn);

    await waitFor(() => {
      const calls = fetchMock.mock.calls;
      // Find the POST call
      const postCall = calls.find(([_url, opts]) => opts && opts.method === 'POST');
      expect(postCall).toBeTruthy();
      const [url, init] = postCall;
      expect(url).toMatch(/\/api\/settings\/hf-token$/);
      const body = JSON.parse(init.body);
      expect(body).toEqual({ token: 'hf_newtoken123' });
    });
  });

  it('Clear button shows confirmation dialog and DELETEs on confirm', async () => {
    const fetchMock = mockFetchSequence(
      { status: 200, body: STATE_APP_ACTIVE },         // initial GET
      { status: 200, body: STATE_THREE_UNSET },        // DELETE response
      { status: 200, body: STATE_THREE_UNSET },        // refetch GET
    );
    global.fetch = fetchMock;

    render(<ApiKeysPanel />);
    await waitFor(() => screen.getByPlaceholderText(/hf_/));

    const clearBtn = screen.getByRole('button', { name: /^clear$/i });
    fireEvent.click(clearBtn);

    // Dialog appears
    expect(screen.getByText(/Clear the App-source HuggingFace token/)).toBeInTheDocument();
    const confirmBtn = screen.getByRole('button', { name: /clear token/i });
    fireEvent.click(confirmBtn);

    await waitFor(() => {
      const calls = fetchMock.mock.calls;
      const del = calls.find(([_u, opts]) => opts && opts.method === 'DELETE');
      expect(del).toBeTruthy();
      expect(del[0]).toMatch(/\/api\/settings\/hf-token/);
      // also_clear_hf_cli default is false → no query string
      expect(del[0]).not.toMatch(/also_clear_hf_cli=true/);
    });
  });

  it('"Test now" button refetches state', async () => {
    const fetchMock = mockFetchSequence(
      { status: 200, body: STATE_THREE_UNSET },
      { status: 200, body: STATE_THREE_UNSET },
    );
    global.fetch = fetchMock;

    render(<ApiKeysPanel />);
    await waitFor(() => screen.getByPlaceholderText(/hf_/));
    const testBtn = screen.getByRole('button', { name: /test now/i });
    fireEvent.click(testBtn);

    await waitFor(() => {
      expect(fetchMock.mock.calls.length).toBeGreaterThanOrEqual(2);
    });
  });
});
