import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { toast } from 'sonner';
import { describe, it, expect, vi, beforeEach } from 'vitest';

const { mockTrackProductEvent } = vi.hoisted(() => ({ mockTrackProductEvent: vi.fn() }));
vi.mock('@/lib/analytics', () => ({ trackProductEvent: mockTrackProductEvent }));

// R3UXA-001: mock copyToClipboard so tests can control success/failure
const { mockCopyToClipboard } = vi.hoisted(() => ({ mockCopyToClipboard: vi.fn() }));
vi.mock('@/lib/clipboard', () => ({ copyToClipboard: mockCopyToClipboard }));

import { server } from '@/test/setup';

vi.mock('@inertiajs/react', async () => {
  const actual = await vi.importActual('@inertiajs/react');
  return {
    ...actual,
    usePage: vi.fn(() => ({
      props: {
        auth: { user: { name: 'Test User', email: 'test@example.com' } },
        features: { notifications: false },
        sites: [],
        limits: null,
        ai_defaults: { model: 'gpt-4o-mini', temperature: 0.7 },
      },
    })),
    Head: () => null,
  };
});

vi.mock('@/Components/theme/use-theme', () => ({
  useTheme: vi.fn(() => ({ theme: 'system', setTheme: vi.fn(), resolvedTheme: 'light' })),
}));

vi.mock('@/Layouts/DashboardLayout', () => ({
  default: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="dashboard-layout">{children}</div>
  ),
}));

vi.mock('sonner', () => ({
  toast: { success: vi.fn(), error: vi.fn() },
}));

import ApiTokens from './ApiTokens';

describe('ApiTokens', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Default: clipboard succeeds
    mockCopyToClipboard.mockResolvedValue(true);
  });

  it('fires settings_api_tokens_viewed and feature_used on mount', async () => {
    server.use(http.get('/api/tokens', () => HttpResponse.json([])));
    render(<ApiTokens />);
    await waitFor(() => {
      expect(mockTrackProductEvent).toHaveBeenCalledWith('settings_api_tokens_viewed');
      expect(mockTrackProductEvent).toHaveBeenCalledWith('feature_used', {
        feature: 'settings_api_tokens',
      });
    });
  });

  it('renders the API Tokens heading', async () => {
    server.use(http.get('/api/tokens', () => HttpResponse.json([])));
    render(<ApiTokens />);
    await waitFor(() => {
      expect(screen.getByText(/api tokens/i)).toBeInTheDocument();
    });
  });

  it('shows empty state when no tokens exist', async () => {
    server.use(http.get('/api/tokens', () => HttpResponse.json([])));
    render(<ApiTokens />);
    await waitFor(() => {
      expect(screen.getByText(/No API tokens created/i)).toBeInTheDocument();
    });
  });

  it('shows Create token button', async () => {
    server.use(http.get('/api/tokens', () => HttpResponse.json([])));
    render(<ApiTokens />);
    await waitFor(() => {
      expect(screen.getByRole('button', { name: /create token/i })).toBeInTheDocument();
    });
  });

  it('opens create token dialog when Create token is clicked', async () => {
    server.use(http.get('/api/tokens', () => HttpResponse.json([])));
    const user = userEvent.setup();
    render(<ApiTokens />);

    await waitFor(() => screen.getByRole('button', { name: /create token/i }));
    await user.click(screen.getAllByRole('button', { name: /create token/i })[0]);

    expect(screen.getByLabelText(/token name/i)).toBeInTheDocument();
  });

  it('renders existing tokens from API', async () => {
    server.use(
      http.get('/api/tokens', () =>
        HttpResponse.json([
          {
            id: 1,
            name: 'My CI Token',
            abilities: ['*'],
            last_used_at: null,
            expires_at: null,
            created_at: '2025-01-01T00:00:00Z',
          },
        ]),
      ),
    );

    render(<ApiTokens />);
    await waitFor(() => {
      expect(screen.getByText('My CI Token')).toBeInTheDocument();
    });
  });

  it('shows error state when token fetch fails', async () => {
    server.use(http.get('/api/tokens', () => HttpResponse.error()));
    render(<ApiTokens />);
    await waitFor(() => {
      expect(screen.getByText(/couldn't load your tokens/i)).toBeInTheDocument();
    });
    expect(screen.getByRole('button', { name: /Try Again/i })).toBeInTheDocument();
  });

  it('copy token button has accessible name after successful creation', async () => {
    server.use(
      http.get('/api/tokens', () => HttpResponse.json([])),
      http.post('/api/tokens', () =>
        HttpResponse.json({ token: 'tok_test_abc123', id: 1, name: 'CI Token', abilities: ['read'] }),
      ),
    );
    const user = userEvent.setup();
    render(<ApiTokens />);

    // Open the create dialog (the Read ability is pre-selected by default)
    await waitFor(() => screen.getByRole('button', { name: /create token/i }));
    await user.click(screen.getAllByRole('button', { name: /create token/i })[0]);

    const nameInput = await screen.findByLabelText(/token name/i);
    await user.type(nameInput, 'CI Token');

    await user.click(screen.getByRole('button', { name: /^create token$/i }));

    expect(await screen.findByRole('button', { name: /copy api token/i })).toBeInTheDocument();
  });

  // ============================================
  // R3UXA-001: hardened copy — clipboard failure shows error toast
  // ============================================

  it('R3UXA-001: shows error toast and no success toast when clipboard copy fails', async () => {
    mockCopyToClipboard.mockResolvedValue(false);
    server.use(
      http.get('/api/tokens', () => HttpResponse.json([])),
      http.post('/api/tokens', () =>
        HttpResponse.json({ token: 'tok_test_abc123', id: 1, name: 'CI Token', abilities: ['read'] }),
      ),
    );
    const user = userEvent.setup();
    render(<ApiTokens />);

    await waitFor(() => screen.getByRole('button', { name: /create token/i }));
    await user.click(screen.getAllByRole('button', { name: /create token/i })[0]);
    const nameInput = await screen.findByLabelText(/token name/i);
    await user.type(nameInput, 'CI Token');
    await user.click(screen.getByRole('button', { name: /^create token$/i }));

    const copyBtn = await screen.findByRole('button', { name: /copy api token/i });

    // clear the "created" success toast so we can assert independently
    vi.mocked(toast.success).mockClear();
    vi.mocked(toast.error).mockClear();

    await user.click(copyBtn);

    expect(vi.mocked(toast.error)).toHaveBeenCalledTimes(1);
    expect(vi.mocked(toast.success)).not.toHaveBeenCalled();
  });

  it('R3UXA-001: shows success toast when clipboard copy succeeds', async () => {
    mockCopyToClipboard.mockResolvedValue(true);
    server.use(
      http.get('/api/tokens', () => HttpResponse.json([])),
      http.post('/api/tokens', () =>
        HttpResponse.json({ token: 'tok_test_abc123', id: 1, name: 'CI Token', abilities: ['read'] }),
      ),
    );
    const user = userEvent.setup();
    render(<ApiTokens />);

    await waitFor(() => screen.getByRole('button', { name: /create token/i }));
    await user.click(screen.getAllByRole('button', { name: /create token/i })[0]);
    const nameInput = await screen.findByLabelText(/token name/i);
    await user.type(nameInput, 'CI Token');
    await user.click(screen.getByRole('button', { name: /^create token$/i }));

    const copyBtn = await screen.findByRole('button', { name: /copy api token/i });

    vi.mocked(toast.success).mockClear();
    vi.mocked(toast.error).mockClear();

    await user.click(copyBtn);

    expect(vi.mocked(toast.success)).toHaveBeenCalledTimes(1);
    expect(vi.mocked(toast.error)).not.toHaveBeenCalled();
  });

  // ============================================
  // R3UXA-002: acknowledgment gate — Done disabled until checkbox ticked;
  //           panel non-dismissible (outside click / Esc do NOT close)
  // ============================================

  it('R3UXA-002: Done button is disabled until acknowledgment checkbox is ticked', async () => {
    server.use(
      http.get('/api/tokens', () => HttpResponse.json([])),
      http.post('/api/tokens', () =>
        HttpResponse.json({ token: 'tok_test_abc123', id: 1, name: 'CI Token', abilities: ['read'] }),
      ),
    );
    const user = userEvent.setup();
    render(<ApiTokens />);

    await waitFor(() => screen.getByRole('button', { name: /create token/i }));
    await user.click(screen.getAllByRole('button', { name: /create token/i })[0]);
    const nameInput = await screen.findByLabelText(/token name/i);
    await user.type(nameInput, 'CI Token');
    await user.click(screen.getByRole('button', { name: /^create token$/i }));

    // Done button should be rendered and disabled
    const doneBtn = await screen.findByRole('button', { name: /^done$/i });
    expect(doneBtn).toBeDisabled();

    // Tick the checkbox
    const checkbox = screen.getByRole('checkbox', { name: /I.ve copied and stored/i });
    await user.click(checkbox);

    expect(doneBtn).not.toBeDisabled();
  });

  // ============================================
  // R3UXA-018: preset buttons populate expiry; blank = never expires
  // ============================================

  it('R3UXA-018: clicking 30 days preset populates the expiry field ~30 days out', async () => {
    server.use(http.get('/api/tokens', () => HttpResponse.json([])));
    const user = userEvent.setup();
    const now = new Date(2026, 0, 1, 12, 0); // 2026-01-01T12:00 local
    vi.setSystemTime(now);

    render(<ApiTokens />);
    await waitFor(() => screen.getByRole('button', { name: /create token/i }));
    await user.click(screen.getAllByRole('button', { name: /create token/i })[0]);

    await user.click(await screen.findByRole('button', { name: /^30 days$/i }));

    const input = screen.getByLabelText(/expiration/i) as HTMLInputElement;
    // Should be 30 days from 2026-01-01 → 2026-01-31
    expect(input.value).toMatch(/^2026-01-31/);

    vi.useRealTimers();
  });

  it('R3UXA-018: clicking No expiry clears the expiry field', async () => {
    server.use(http.get('/api/tokens', () => HttpResponse.json([])));
    const user = userEvent.setup();

    render(<ApiTokens />);
    await waitFor(() => screen.getByRole('button', { name: /create token/i }));
    await user.click(screen.getAllByRole('button', { name: /create token/i })[0]);

    // First set a value, then clear it
    await user.click(await screen.findByRole('button', { name: /^30 days$/i }));
    await user.click(screen.getByRole('button', { name: /no expiry/i }));

    const input = screen.getByLabelText(/expiration/i) as HTMLInputElement;
    expect(input.value).toBe('');
  });

  // ============================================
  // R3FE-009: datetime-local min uses local time, not UTC
  // ============================================

  it('R3FE-009: expiry input min is in local wall-clock time (not UTC)', async () => {
    server.use(http.get('/api/tokens', () => HttpResponse.json([])));
    const user = userEvent.setup();
    // UTC-8 scenario: local 2026-01-01T08:00, UTC 2026-01-01T16:00
    // localDatetimeLocal should produce "2026-01-01T08:00", NOT "2026-01-01T16:00"
    const localTime = new Date(2026, 0, 1, 8, 0); // Jan 1 2026 at 08:00 local
    vi.setSystemTime(localTime);

    render(<ApiTokens />);
    await waitFor(() => screen.getByRole('button', { name: /create token/i }));
    await user.click(screen.getAllByRole('button', { name: /create token/i })[0]);

    const input = await screen.findByLabelText(/expiration/i) as HTMLInputElement;
    // The min attribute must use local hour (08), not UTC (variable)
    expect(input.min).toBe('2026-01-01T08:00');

    vi.useRealTimers();
  });
});
