import { describe, it, expect, vi, beforeEach } from 'vitest';

// Mock apiClient
vi.mock('../client', () => ({
  default: {
    get: vi.fn(),
    post: vi.fn(),
    delete: vi.fn(),
  },
}));

import {
  getOAuthProviders,
  initiateOAuth,
  handleOAuthCallback,
  getOAuthConnections,
  disconnectOAuth,
} from '../oauth';
import apiClient from '../client';

describe('oauth API', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('getOAuthProviders', () => {
    it('fetches list of enabled OAuth providers', async () => {
      const mockProviders = [
        {
          name: 'google',
          display_name: 'Google',
          icon: 'google-icon.svg',
        },
        {
          name: 'microsoft',
          display_name: 'Microsoft',
          icon: 'microsoft-icon.svg',
        },
        {
          name: 'github',
          display_name: 'GitHub',
          icon: 'github-icon.svg',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({
        data: { providers: mockProviders },
      });

      const result = await getOAuthProviders();

      expect(apiClient.get).toHaveBeenCalledWith('/auth/oauth/providers/');
      expect(result).toEqual(mockProviders);
    });

    it('returns empty array when no providers enabled', async () => {
      vi.mocked(apiClient.get).mockResolvedValue({
        data: { providers: [] },
      });

      const result = await getOAuthProviders();

      expect(result).toEqual([]);
    });

    it('extracts providers from nested response', async () => {
      const mockProviders = [
        {
          name: 'google',
          display_name: 'Google',
          icon: 'google-icon.svg',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({
        data: { providers: mockProviders },
      });

      const result = await getOAuthProviders();

      // Verify it returns response.data.providers, not response.data
      expect(result).toEqual(mockProviders);
      expect(Array.isArray(result)).toBe(true);
    });
  });

  describe('initiateOAuth', () => {
    it('initiates OAuth flow for Google', async () => {
      const mockResponse = {
        authorization_url: 'https://accounts.google.com/o/oauth2/auth?client_id=123&redirect_uri=...',
      };
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });

      const result = await initiateOAuth('google');

      expect(apiClient.get).toHaveBeenCalledWith('/auth/oauth/google/authorize/');
      expect(result).toEqual(mockResponse);
      expect(result.authorization_url).toContain('accounts.google.com');
    });

    it('initiates OAuth flow for Microsoft', async () => {
      const mockResponse = {
        authorization_url: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=...',
      };
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });

      const result = await initiateOAuth('microsoft');

      expect(apiClient.get).toHaveBeenCalledWith('/auth/oauth/microsoft/authorize/');
      expect(result).toEqual(mockResponse);
    });

    it('initiates OAuth flow for GitHub', async () => {
      const mockResponse = {
        authorization_url: 'https://github.com/login/oauth/authorize?client_id=xyz&scope=...',
      };
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });

      const result = await initiateOAuth('github');

      expect(apiClient.get).toHaveBeenCalledWith('/auth/oauth/github/authorize/');
      expect(result.authorization_url).toContain('github.com');
    });

    it('includes state parameter in authorization URL', async () => {
      const mockResponse = {
        authorization_url: 'https://provider.com/auth?state=random-state-token',
      };
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });

      const result = await initiateOAuth('google');

      expect(result.authorization_url).toContain('state=');
    });
  });

  describe('handleOAuthCallback', () => {
    it('exchanges authorization code for tokens', async () => {
      const mockResponse = {
        access: 'access-token-123',
        refresh: 'refresh-token-456',
        user: {
          id: 1,
          username: 'johndoe',
          email: 'john@example.com',
          name: 'John Doe',
          role: 'owner',
          is_staff: false,
          is_superuser: false,
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });

      const result = await handleOAuthCallback('google', 'auth-code-xyz', 'state-token-abc');

      expect(apiClient.post).toHaveBeenCalledWith('/auth/oauth/google/callback/', {
        code: 'auth-code-xyz',
        state: 'state-token-abc',
      });
      expect(result).toEqual(mockResponse);
      expect(result.access).toBe('access-token-123');
      expect(result.refresh).toBe('refresh-token-456');
      expect(result.user.email).toBe('john@example.com');
    });

    it('handles callback with business user', async () => {
      const mockResponse = {
        access: 'access-token',
        refresh: 'refresh-token',
        user: {
          id: 2,
          username: 'staffmember',
          email: 'staff@business.com',
          name: 'Staff Member',
          role: 'staff',
          is_staff: true,
          is_superuser: false,
          business: 5,
          business_name: 'My Business',
          business_subdomain: 'mybiz',
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });

      const result = await handleOAuthCallback('microsoft', 'code-123', 'state-456');

      expect(result.user.business).toBe(5);
      expect(result.user.business_name).toBe('My Business');
      expect(result.user.business_subdomain).toBe('mybiz');
    });

    it('handles callback with avatar URL', async () => {
      const mockResponse = {
        access: 'access-token',
        refresh: 'refresh-token',
        user: {
          id: 3,
          username: 'user',
          email: 'user@example.com',
          name: 'User Name',
          role: 'customer',
          avatar_url: 'https://avatar.com/user.jpg',
          is_staff: false,
          is_superuser: false,
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });

      const result = await handleOAuthCallback('github', 'code-abc', 'state-def');

      expect(result.user.avatar_url).toBe('https://avatar.com/user.jpg');
    });

    it('handles superuser login via OAuth', async () => {
      const mockResponse = {
        access: 'admin-access-token',
        refresh: 'admin-refresh-token',
        user: {
          id: 1,
          username: 'admin',
          email: 'admin@platform.com',
          name: 'Platform Admin',
          role: 'superuser',
          is_staff: true,
          is_superuser: true,
        },
      };
      vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });

      const result = await handleOAuthCallback('google', 'admin-code', 'admin-state');

      expect(result.user.is_superuser).toBe(true);
      expect(result.user.role).toBe('superuser');
    });

    it('sends correct data for different providers', async () => {
      vi.mocked(apiClient.post).mockResolvedValue({
        data: {
          access: 'token',
          refresh: 'token',
          user: { id: 1, email: 'test@test.com', name: 'Test', role: 'owner', is_staff: false, is_superuser: false, username: 'test' },
        },
      });

      await handleOAuthCallback('github', 'code-1', 'state-1');
      expect(apiClient.post).toHaveBeenCalledWith('/auth/oauth/github/callback/', {
        code: 'code-1',
        state: 'state-1',
      });

      await handleOAuthCallback('microsoft', 'code-2', 'state-2');
      expect(apiClient.post).toHaveBeenCalledWith('/auth/oauth/microsoft/callback/', {
        code: 'code-2',
        state: 'state-2',
      });
    });
  });

  describe('getOAuthConnections', () => {
    it('fetches list of connected OAuth accounts', async () => {
      const mockConnections = [
        {
          id: 'conn-1',
          provider: 'google',
          provider_user_id: 'google-user-123',
          email: 'user@gmail.com',
          connected_at: '2024-01-15T10:30:00Z',
        },
        {
          id: 'conn-2',
          provider: 'microsoft',
          provider_user_id: 'ms-user-456',
          email: 'user@outlook.com',
          connected_at: '2024-02-20T14:45:00Z',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({
        data: { connections: mockConnections },
      });

      const result = await getOAuthConnections();

      expect(apiClient.get).toHaveBeenCalledWith('/auth/oauth/connections/');
      expect(result).toEqual(mockConnections);
      expect(result).toHaveLength(2);
    });

    it('returns empty array when no connections exist', async () => {
      vi.mocked(apiClient.get).mockResolvedValue({
        data: { connections: [] },
      });

      const result = await getOAuthConnections();

      expect(result).toEqual([]);
    });

    it('extracts connections from nested response', async () => {
      const mockConnections = [
        {
          id: 'conn-1',
          provider: 'github',
          provider_user_id: 'github-123',
          connected_at: '2024-03-01T09:00:00Z',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({
        data: { connections: mockConnections },
      });

      const result = await getOAuthConnections();

      // Verify it returns response.data.connections, not response.data
      expect(result).toEqual(mockConnections);
      expect(Array.isArray(result)).toBe(true);
    });

    it('handles connections without email field', async () => {
      const mockConnections = [
        {
          id: 'conn-1',
          provider: 'github',
          provider_user_id: 'github-user-789',
          connected_at: '2024-04-10T12:00:00Z',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({
        data: { connections: mockConnections },
      });

      const result = await getOAuthConnections();

      expect(result[0].email).toBeUndefined();
      expect(result[0].provider).toBe('github');
    });

    it('handles multiple connections from same provider', async () => {
      const mockConnections = [
        {
          id: 'conn-1',
          provider: 'google',
          provider_user_id: 'google-user-1',
          email: 'work@gmail.com',
          connected_at: '2024-01-01T00:00:00Z',
        },
        {
          id: 'conn-2',
          provider: 'google',
          provider_user_id: 'google-user-2',
          email: 'personal@gmail.com',
          connected_at: '2024-01-02T00:00:00Z',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({
        data: { connections: mockConnections },
      });

      const result = await getOAuthConnections();

      expect(result).toHaveLength(2);
      expect(result.filter((c) => c.provider === 'google')).toHaveLength(2);
    });
  });

  describe('disconnectOAuth', () => {
    it('disconnects Google OAuth account', async () => {
      vi.mocked(apiClient.delete).mockResolvedValue({});

      await disconnectOAuth('google');

      expect(apiClient.delete).toHaveBeenCalledWith('/auth/oauth/connections/google/');
    });

    it('disconnects Microsoft OAuth account', async () => {
      vi.mocked(apiClient.delete).mockResolvedValue({});

      await disconnectOAuth('microsoft');

      expect(apiClient.delete).toHaveBeenCalledWith('/auth/oauth/connections/microsoft/');
    });

    it('disconnects GitHub OAuth account', async () => {
      vi.mocked(apiClient.delete).mockResolvedValue({});

      await disconnectOAuth('github');

      expect(apiClient.delete).toHaveBeenCalledWith('/auth/oauth/connections/github/');
    });

    it('returns void on successful disconnect', async () => {
      vi.mocked(apiClient.delete).mockResolvedValue({});

      const result = await disconnectOAuth('google');

      expect(result).toBeUndefined();
    });

    it('handles disconnect for custom provider', async () => {
      vi.mocked(apiClient.delete).mockResolvedValue({});

      await disconnectOAuth('custom-provider');

      expect(apiClient.delete).toHaveBeenCalledWith('/auth/oauth/connections/custom-provider/');
    });
  });

  describe('error handling', () => {
    it('propagates errors from getOAuthProviders', async () => {
      const error = new Error('Network error');
      vi.mocked(apiClient.get).mockRejectedValue(error);

      await expect(getOAuthProviders()).rejects.toThrow('Network error');
    });

    it('propagates errors from initiateOAuth', async () => {
      const error = new Error('Provider not configured');
      vi.mocked(apiClient.get).mockRejectedValue(error);

      await expect(initiateOAuth('google')).rejects.toThrow('Provider not configured');
    });

    it('propagates errors from handleOAuthCallback', async () => {
      const error = new Error('Invalid authorization code');
      vi.mocked(apiClient.post).mockRejectedValue(error);

      await expect(handleOAuthCallback('google', 'bad-code', 'state')).rejects.toThrow('Invalid authorization code');
    });

    it('propagates errors from getOAuthConnections', async () => {
      const error = new Error('Unauthorized');
      vi.mocked(apiClient.get).mockRejectedValue(error);

      await expect(getOAuthConnections()).rejects.toThrow('Unauthorized');
    });

    it('propagates errors from disconnectOAuth', async () => {
      const error = new Error('Connection not found');
      vi.mocked(apiClient.delete).mockRejectedValue(error);

      await expect(disconnectOAuth('google')).rejects.toThrow('Connection not found');
    });
  });
});
