import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter } from 'react-router';
import type { ReactNode } from 'react';

vi.mock('../api/client', () => ({ api: { GET: vi.fn() } }));
vi.mock('./auth-store', () => ({
  useAuthStore: Object.assign(
    () => ({ isAuthenticated: false, login: vi.fn(), loading: false, error: null }),
    { setState: vi.fn() }
  ),
}));

import { api as apiClient } from '../api/client';
import { useAuthStore } from './auth-store';
import { LoginPage } from './LoginPage';

function wrapper(initialEntries: string[]) {
  return ({ children }: { children: ReactNode }) => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    return (
      <QueryClientProvider client={qc}>
        <MemoryRouter initialEntries={initialEntries}>{children}</MemoryRouter>
      </QueryClientProvider>
    );
  };
}

function mockCaps(body: any) {
  (apiClient.GET as any).mockImplementation((path: string) => {
    if (path === '/auth/capabilities') return Promise.resolve({ data: body, error: null });
    if (path === '/auth/oidc/config') return Promise.resolve({
      data: {
        clientId: 'spa-client',
        authorizationEndpoint: 'https://auth.logto.example/oidc/auth',
        resource: 'https://api.cameleer.local',
        additionalScopes: [],
      },
      error: null,
    });
    return Promise.resolve({ data: undefined, error: { message: 'unexpected' } });
  });
}

describe('LoginPage', () => {
  beforeEach(() => vi.clearAllMocks());

  it('SSO primary, no ?local: renders SSO button only and admin-recovery link, no local form', async () => {
    mockCaps({
      oidc: { enabled: true, providerName: 'Logto', primary: true },
      localAccounts: { enabled: true, adminRecoveryOnly: true },
    });

    render(<LoginPage />, { wrapper: wrapper(['/login']) });

    expect(await screen.findByRole('button', { name: /sign in with logto/i })).toBeInTheDocument();
    expect(screen.queryByLabelText(/username/i)).toBeNull();
    expect(screen.queryByLabelText(/password/i)).toBeNull();
    expect(screen.getByRole('link', { name: /admin recovery/i })).toBeInTheDocument();
  });

  it('SSO primary, ?local present: renders local form with amber recovery banner and back-to-SSO link', async () => {
    mockCaps({
      oidc: { enabled: true, providerName: 'Logto', primary: true },
      localAccounts: { enabled: true, adminRecoveryOnly: true },
    });

    render(<LoginPage />, { wrapper: wrapper(['/login?local']) });

    expect(await screen.findByLabelText(/username/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
    expect(screen.getByText(/admin recovery/i)).toBeInTheDocument();
    expect(screen.getByRole('link', { name: /back to sso/i })).toBeInTheDocument();
  });

  it('OIDC disabled: renders local form only, no SSO button', async () => {
    mockCaps({
      oidc: { enabled: false, providerName: '', primary: false },
      localAccounts: { enabled: true, adminRecoveryOnly: false },
    });

    render(<LoginPage />, { wrapper: wrapper(['/login']) });

    expect(await screen.findByLabelText(/username/i)).toBeInTheDocument();
    expect(screen.queryByRole('button', { name: /sign in with/i })).toBeNull();
    expect(screen.queryByText(/admin recovery/i)).toBeNull();
  });

  it('capabilities request fails: renders degraded local form with warning banner', async () => {
    (apiClient.GET as any).mockImplementation((path: string) => {
      if (path === '/auth/capabilities') return Promise.resolve({ data: undefined, error: { message: 'fail' } });
      return Promise.resolve({ data: undefined, error: { message: 'unexpected' } });
    });

    render(<LoginPage />, { wrapper: wrapper(['/login']) });

    expect(await screen.findByLabelText(/username/i)).toBeInTheDocument();
    expect(screen.getByText(/sign-in options couldn't load/i)).toBeInTheDocument();
  });

  it('SSO button click: navigates to authorize URL WITHOUT prompt=none', async () => {
    mockCaps({
      oidc: { enabled: true, providerName: 'Logto', primary: true },
      localAccounts: { enabled: true, adminRecoveryOnly: true },
    });

    const originalLocation = window.location;
    const hrefSetter = vi.fn();
    Object.defineProperty(window, 'location', {
      configurable: true,
      value: { ...originalLocation, get href() { return ''; }, set href(v: string) { hrefSetter(v); } },
    });

    try {
      render(<LoginPage />, { wrapper: wrapper(['/login']) });
      const btn = await screen.findByRole('button', { name: /sign in with logto/i });
      fireEvent.click(btn);

      await waitFor(() => expect(hrefSetter).toHaveBeenCalled());
      const url: string = hrefSetter.mock.calls[0][0];
      expect(url).toMatch(/^https:\/\/auth\.logto\.example\/oidc\/auth\?/);
      expect(url).not.toMatch(/prompt=none/);
      expect(url).toMatch(/response_type=code/);
      expect(url).toMatch(/client_id=spa-client/);
      expect(url).toMatch(/scope=/);
    } finally {
      Object.defineProperty(window, 'location', { configurable: true, value: originalLocation });
    }
  });

  it('SSO button click: when /auth/oidc/config fails, button unlocks and error is set', async () => {
    const setStateMock = vi.fn();
    const useAuthStoreMock = vi.mocked(useAuthStore) as unknown as { setState: typeof setStateMock };
    useAuthStoreMock.setState = setStateMock;

    (apiClient.GET as any).mockImplementation((path: string) => {
      if (path === '/auth/capabilities') return Promise.resolve({
        data: { oidc: { enabled: true, providerName: 'Logto', primary: true }, localAccounts: { enabled: true, adminRecoveryOnly: true } },
        error: null,
      });
      if (path === '/auth/oidc/config') return Promise.reject(new Error('network down'));
      return Promise.resolve({ data: undefined, error: { message: 'unexpected' } });
    });

    render(<LoginPage />, { wrapper: wrapper(['/login']) });
    const btn = await screen.findByRole('button', { name: /sign in with logto/i });
    fireEvent.click(btn);

    await waitFor(() => expect(setStateMock).toHaveBeenCalled());
    const errorPayload = setStateMock.mock.calls[0][0];
    expect(errorPayload.error).toMatch(/OIDC configuration unavailable/i);
    // Button should not stay locked in "Redirecting…"
    await waitFor(() => expect(btn).not.toHaveTextContent(/redirecting/i));
  });
});
