import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useAuthStore } from './auth';
import * as authService from '@/services/api/auth';
import { TokenStorage } from '@/services/tokenStorage';
import { csrfService } from '@/services/csrf';

// Mock dependencies
vi.mock('@/services/api/auth');
vi.mock('@/services/tokenStorage', () => ({
  TokenStorage: {
    clearTokens: vi.fn(),
    hasTokens: vi.fn(),
  },
}));
vi.mock('@/services/csrf', () => ({
  csrfService: {
    refreshToken: vi.fn(),
    clearToken: vi.fn(),
  },
}));
vi.mock('@/services/tokenRefresh', () => ({
  initializeProactiveRefresh: vi.fn(),
  cleanupProactiveRefresh: vi.fn(),
}));

const mockUser = {
  id: '1',
  username: 'testuser',
  email: 'test@example.com',
  first_name: 'Test',
  last_name: 'User',
  role: 'user' as const,
  is_active: true,
  is_verified: true,
  created_at: '2024-01-01T00:00:00Z',
};

describe('AuthStore', () => {
  beforeEach(() => {
    // Reset store state
    useAuthStore.setState({
      user: null,
      isAuthenticated: false,
      isLoading: false,
      error: null,
    });
    vi.clearAllMocks();
  });

  describe('Initial State', () => {
    it('should have correct initial state', () => {
      const state = useAuthStore.getState();
      expect(state.user).toBeNull();
      expect(state.isAuthenticated).toBe(false);
      expect(state.isLoading).toBe(false);
      expect(state.error).toBeNull();
    });
  });

  describe('login', () => {
    it('should login successfully and update state', async () => {
      const mockResponse = {
        access_token: 'access-token',
        refresh_token: 'refresh-token',
        user: mockUser,
      };

      vi.mocked(authService.login).mockResolvedValue(mockResponse);
      vi.mocked(csrfService.refreshToken).mockResolvedValue(undefined);

      await useAuthStore.getState().login({
        email: 'test@example.com',
        password: 'password123',
      });

      const state = useAuthStore.getState();
      expect(state.user).toEqual(mockUser);
      expect(state.isAuthenticated).toBe(true);
      expect(state.isLoading).toBe(false);
      expect(state.error).toBeNull();
      expect(authService.login).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123',
      });
      expect(csrfService.refreshToken).toHaveBeenCalled();
    });

    it('should handle login error', async () => {
      const mockError = {
        message: 'Invalid credentials',
        code: '401',
      };

      vi.mocked(authService.login).mockRejectedValue(mockError);

      try {
        await useAuthStore.getState().login({
          email: 'test@example.com',
          password: 'wrongpassword',
        });
      } catch (error) {
        // Expected to throw
      }

      // Wait a bit for state to settle
      await new Promise((resolve) => setTimeout(resolve, 10));

      const state = useAuthStore.getState();
      expect(state.user).toBeNull();
      expect(state.isAuthenticated).toBe(false);
      expect(state.isLoading).toBe(false);
    });

    it('should set loading state during login', async () => {
      let resolveLogin: (value: any) => void;
      const loginPromise = new Promise((resolve) => {
        resolveLogin = resolve;
      });

      vi.mocked(authService.login).mockReturnValue(loginPromise as any);

      const loginPromise2 = useAuthStore.getState().login({
        email: 'test@example.com',
        password: 'password123',
      });

      expect(useAuthStore.getState().isLoading).toBe(true);

      resolveLogin!({
        access_token: 'token',
        refresh_token: 'refresh',
        user: mockUser,
      });
      vi.mocked(csrfService.refreshToken).mockResolvedValue(undefined);

      await loginPromise2;
      expect(useAuthStore.getState().isLoading).toBe(false);
    });
  });

  describe('register', () => {
    it('should register successfully and update state', async () => {
      const mockResponse = {
        access_token: 'access-token',
        refresh_token: 'refresh-token',
        user: mockUser,
      };

      vi.mocked(authService.register).mockResolvedValue(mockResponse);
      vi.mocked(csrfService.refreshToken).mockResolvedValue(undefined);

      await useAuthStore.getState().register({
        email: 'newuser@example.com',
        password: 'password123',
        confirmPassword: 'password123',
        username: 'newuser',
      });

      const state = useAuthStore.getState();
      expect(state.user).toEqual(mockUser);
      expect(state.isAuthenticated).toBe(true);
      expect(state.isLoading).toBe(false);
      expect(state.error).toBeNull();
    });

    it('should handle registration error', async () => {
      const mockError = {
        message: 'Email already exists',
        code: '409',
      };

      vi.mocked(authService.register).mockRejectedValue(mockError);

      try {
        await useAuthStore.getState().register({
          email: 'existing@example.com',
          password: 'password123',
          confirmPassword: 'password123',
          username: 'existinguser',
        });
      } catch (error) {
        // Expected to throw
      }

      const state = useAuthStore.getState();
      expect(state.user).toBeNull();
      expect(state.isAuthenticated).toBe(false);
    });
  });

  describe('logout', () => {
    it('should logout successfully and clear state', async () => {
      // Set initial authenticated state
      useAuthStore.setState({
        user: mockUser,
        isAuthenticated: true,
      });

      vi.mocked(authService.logout).mockResolvedValue(undefined);

      await useAuthStore.getState().logout();

      const state = useAuthStore.getState();
      expect(state.user).toBeNull();
      expect(state.isAuthenticated).toBe(false);
      expect(authService.logout).toHaveBeenCalled();
    });

    it('should handle logout error gracefully', async () => {
      useAuthStore.setState({
        user: mockUser,
        isAuthenticated: true,
      });

      vi.mocked(authService.logout).mockRejectedValue(
        new Error('Logout failed'),
      );

      await useAuthStore.getState().logout();

      // State should still be cleared even if API call fails
      const state = useAuthStore.getState();
      expect(state.user).toBeNull();
      expect(state.isAuthenticated).toBe(false);
    });
  });

  describe('refreshUser', () => {
    it('should refresh user data successfully', async () => {
      useAuthStore.setState({
        user: mockUser,
        isAuthenticated: true,
      });

      const updatedUser = { ...mockUser, first_name: 'Updated' };
      vi.mocked(authService.getMe).mockResolvedValue(updatedUser);
      vi.mocked(TokenStorage.hasTokens).mockReturnValue(true);

      await useAuthStore.getState().refreshUser();

      const state = useAuthStore.getState();
      expect(state.user).toEqual(updatedUser);
      expect(state.isAuthenticated).toBe(true);
    });

    it('should handle refresh error', async () => {
      useAuthStore.setState({
        user: mockUser,
        isAuthenticated: true,
      });

      const mockError = {
        message: 'Unauthorized',
        code: '401',
      };

      vi.mocked(authService.getMe).mockRejectedValue(mockError);
      vi.mocked(TokenStorage.hasTokens).mockReturnValue(true);

      await useAuthStore.getState().refreshUser();

      const state = useAuthStore.getState();
      expect(state.user).toBeNull();
      expect(state.isAuthenticated).toBe(false);
    });

    it('should not refresh if no tokens', async () => {
      vi.mocked(TokenStorage.hasTokens).mockReturnValue(false);

      await useAuthStore.getState().refreshUser();

      const state = useAuthStore.getState();
      expect(state.user).toBeNull();
      expect(state.isAuthenticated).toBe(false);
      expect(authService.getMe).not.toHaveBeenCalled();
    });
  });

  describe('checkAuthStatus', () => {
    it('should check auth status and set user if authenticated', async () => {
      vi.mocked(authService.getMe).mockResolvedValue(mockUser);
      vi.mocked(TokenStorage.hasTokens).mockReturnValue(true);

      await useAuthStore.getState().checkAuthStatus();

      const state = useAuthStore.getState();
      expect(state.user).toEqual(mockUser);
      expect(state.isAuthenticated).toBe(true);
      expect(state.isLoading).toBe(false);
    });

    it('should handle check auth status error', async () => {
      vi.mocked(authService.getMe).mockRejectedValue({
        message: 'Not authenticated',
        code: '401',
      });
      vi.mocked(TokenStorage.hasTokens).mockReturnValue(true);

      await useAuthStore.getState().checkAuthStatus();

      const state = useAuthStore.getState();
      expect(state.user).toBeNull();
      expect(state.isAuthenticated).toBe(false);
      expect(state.isLoading).toBe(false);
    });
  });

  describe('clearError', () => {
    it('should clear error state', () => {
      useAuthStore.setState({
        error: { message: 'Some error', code: '500' },
      });

      useAuthStore.getState().clearError();

      expect(useAuthStore.getState().error).toBeNull();
    });
  });

  describe('setLoading', () => {
    it('should set loading state', () => {
      useAuthStore.getState().setLoading(true);
      expect(useAuthStore.getState().isLoading).toBe(true);

      useAuthStore.getState().setLoading(false);
      expect(useAuthStore.getState().isLoading).toBe(false);
    });
  });
});
