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

// Mock localStorage BEFORE anything imports auth store
// vi.hoisted runs before vi.mock and imports
const mockStorage: Record<string, string> = {};
const mockLocalStorage = {
  getItem: vi.fn((key: string) => mockStorage[key] || null),
  setItem: vi.fn((key: string, value: string) => { mockStorage[key] = value; }),
  removeItem: vi.fn((key: string) => { delete mockStorage[key]; }),
  clear: vi.fn(() => { Object.keys(mockStorage).forEach(k => delete mockStorage[k]); }),
  get length() { return Object.keys(mockStorage).length; },
  key: vi.fn((i: number) => Object.keys(mockStorage)[i] || null),
};

vi.stubGlobal('localStorage', mockLocalStorage);

// Mock the api module before importing the store
vi.mock('../../src/lib/api', () => ({
  authApi: {
    login: vi.fn(),
    register: vi.fn(),
    refreshToken: vi.fn(),
    getMe: vi.fn(),
  },
}));

import { useAuthStore } from '../../src/store/auth';
import { authApi } from '../../src/lib/api';

const mockedAuthApi = vi.mocked(authApi);

describe('Auth Store', () => {
  beforeEach(() => {
    Object.keys(mockStorage).forEach(k => delete mockStorage[k]);
    vi.clearAllMocks();
    // Reset store to initial state
    useAuthStore.setState({
      user: null,
      accessToken: null,
      refreshToken: null,
      isAuthenticated: false,
      isLoading: true,
      quota: null,
    });
  });

  describe('Initial state', () => {
    it('should have correct default values', () => {
      const state = useAuthStore.getState();
      expect(state.user).toBeNull();
      expect(state.isAuthenticated).toBe(false);
      expect(state.isLoading).toBe(true);
      expect(state.quota).toBeNull();
    });
  });

  describe('login', () => {
    it('should login successfully and store tokens', async () => {
      mockedAuthApi.login.mockResolvedValue({
        data: {
          user: { id: 1, username: 'testuser', email: 'test@test.com', is_staff: false },
          tokens: { access: 'access-token-123', refresh: 'refresh-token-456' },
        },
      } as any);
      mockedAuthApi.getMe.mockResolvedValue({
        data: {
          id: 1, username: 'testuser', email: 'test@test.com', is_staff: false,
          quota: { daily_limit: 50, daily_used: 5, monthly_limit: 500, monthly_used: 100 },
        },
      } as any);

      await useAuthStore.getState().login('testuser', 'password123');

      const state = useAuthStore.getState();
      expect(state.user?.username).toBe('testuser');
      expect(state.isAuthenticated).toBe(true);
      expect(state.accessToken).toBe('access-token-123');
      expect(state.refreshToken).toBe('refresh-token-456');
      expect(mockLocalStorage.setItem).toHaveBeenCalledWith('access_token', 'access-token-123');
      expect(mockLocalStorage.setItem).toHaveBeenCalledWith('refresh_token', 'refresh-token-456');
    });

    it('should call authApi.login with correct args', async () => {
      mockedAuthApi.login.mockResolvedValue({
        data: {
          user: { id: 1, username: 'u', email: 'e', is_staff: false },
          tokens: { access: 'a', refresh: 'r' },
        },
      } as any);
      mockedAuthApi.getMe.mockResolvedValue({
        data: { id: 1, username: 'u', email: 'e', is_staff: false, quota: { daily_limit: 50, daily_used: 0, monthly_limit: 500, monthly_used: 0 } },
      } as any);

      await useAuthStore.getState().login('myuser', 'mypass');
      expect(mockedAuthApi.login).toHaveBeenCalledWith('myuser', 'mypass');
    });

    it('should throw on login failure', async () => {
      mockedAuthApi.login.mockRejectedValue(new Error('Invalid credentials'));

      await expect(useAuthStore.getState().login('bad', 'cred')).rejects.toThrow('Invalid credentials');
      expect(useAuthStore.getState().isAuthenticated).toBe(false);
    });

    it('should fetch user info (quota) after login', async () => {
      mockedAuthApi.login.mockResolvedValue({
        data: {
          user: { id: 1, username: 'u', email: 'e', is_staff: false },
          tokens: { access: 'a', refresh: 'r' },
        },
      } as any);
      mockedAuthApi.getMe.mockResolvedValue({
        data: {
          id: 1, username: 'u', email: 'e', is_staff: false,
          quota: { daily_limit: 50, daily_used: 10, monthly_limit: 500, monthly_used: 200 },
        },
      } as any);

      await useAuthStore.getState().login('u', 'p');
      expect(mockedAuthApi.getMe).toHaveBeenCalled();
      expect(useAuthStore.getState().quota?.daily_used).toBe(10);
    });
  });

  describe('register', () => {
    it('should register and auto-login', async () => {
      mockedAuthApi.register.mockResolvedValue({
        data: {
          user: { id: 2, username: 'newuser', email: 'new@test.com', is_staff: false },
          tokens: { access: 'new-access', refresh: 'new-refresh' },
        },
      } as any);
      mockedAuthApi.getMe.mockResolvedValue({
        data: {
          id: 2, username: 'newuser', email: 'new@test.com', is_staff: false,
          quota: { daily_limit: 50, daily_used: 0, monthly_limit: 500, monthly_used: 0 },
        },
      } as any);

      await useAuthStore.getState().register('newuser', 'new@test.com', 'pass123');

      expect(mockedAuthApi.register).toHaveBeenCalledWith('newuser', 'new@test.com', 'pass123');
      expect(useAuthStore.getState().isAuthenticated).toBe(true);
      expect(useAuthStore.getState().user?.username).toBe('newuser');
      expect(mockLocalStorage.setItem).toHaveBeenCalledWith('access_token', 'new-access');
    });

    it('should throw on register failure', async () => {
      mockedAuthApi.register.mockRejectedValue(new Error('Username taken'));
      await expect(useAuthStore.getState().register('taken', 'e@e.com', 'p')).rejects.toThrow();
    });
  });

  describe('logout', () => {
    it('should clear all auth state and localStorage', () => {
      useAuthStore.setState({
        user: { id: 1, username: 'u', email: 'e', is_staff: false },
        accessToken: 'tok',
        refreshToken: 'ref',
        isAuthenticated: true,
        quota: { daily_limit: 50, daily_used: 5, monthly_limit: 500, monthly_used: 100 },
      });

      useAuthStore.getState().logout();

      const state = useAuthStore.getState();
      expect(state.user).toBeNull();
      expect(state.accessToken).toBeNull();
      expect(state.refreshToken).toBeNull();
      expect(state.isAuthenticated).toBe(false);
      expect(state.quota).toBeNull();
      expect(mockLocalStorage.removeItem).toHaveBeenCalledWith('access_token');
      expect(mockLocalStorage.removeItem).toHaveBeenCalledWith('refresh_token');
    });
  });

  describe('refreshAccessToken', () => {
    it('should refresh access token using refresh token', async () => {
      useAuthStore.setState({ refreshToken: 'valid-refresh' });
      mockedAuthApi.refreshToken.mockResolvedValue({ data: { access: 'new-access-token' } } as any);

      await useAuthStore.getState().refreshAccessToken();

      expect(mockedAuthApi.refreshToken).toHaveBeenCalledWith('valid-refresh');
      expect(useAuthStore.getState().accessToken).toBe('new-access-token');
      expect(mockLocalStorage.setItem).toHaveBeenCalledWith('access_token', 'new-access-token');
    });

    it('should throw when no refresh token exists', async () => {
      useAuthStore.setState({ refreshToken: null });
      await expect(useAuthStore.getState().refreshAccessToken()).rejects.toThrow('No refresh token');
    });
  });

  describe('fetchUserInfo', () => {
    it('should fetch and set user info and quota', async () => {
      mockedAuthApi.getMe.mockResolvedValue({
        data: {
          id: 1, username: 'test', email: 'test@test.com', is_staff: true,
          quota: { daily_limit: 100, daily_used: 25, monthly_limit: 1000, monthly_used: 300 },
        },
      } as any);

      await useAuthStore.getState().fetchUserInfo();

      const state = useAuthStore.getState();
      expect(state.user?.username).toBe('test');
      expect(state.user?.is_staff).toBe(true);
      expect(state.isAuthenticated).toBe(true);
      expect(state.quota?.daily_limit).toBe(100);
      expect(state.quota?.daily_used).toBe(25);
    });

    it('should logout on API failure (invalid token)', async () => {
      useAuthStore.setState({
        user: { id: 1, username: 'u', email: 'e', is_staff: false },
        isAuthenticated: true,
      });
      mockedAuthApi.getMe.mockRejectedValue(new Error('401'));

      await useAuthStore.getState().fetchUserInfo();

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

  describe('initialize', () => {
    it('should set isLoading to false after initialization with no token', async () => {
      mockLocalStorage.getItem.mockReturnValue(null);
      await useAuthStore.getState().initialize();

      expect(useAuthStore.getState().isLoading).toBe(false);
      expect(mockedAuthApi.getMe).not.toHaveBeenCalled();
    });

    it('should fetch user info when token exists in localStorage', async () => {
      mockLocalStorage.getItem.mockReturnValue('stored-access-token');
      mockedAuthApi.getMe.mockResolvedValue({
        data: {
          id: 1, username: 'stored', email: 's@t.com', is_staff: false,
          quota: { daily_limit: 50, daily_used: 0, monthly_limit: 500, monthly_used: 0 },
        },
      } as any);

      await useAuthStore.getState().initialize();

      expect(useAuthStore.getState().isLoading).toBe(false);
      expect(useAuthStore.getState().user?.username).toBe('stored');
    });

    it('should logout if token is invalid during initialization', async () => {
      mockLocalStorage.getItem.mockReturnValue('expired-token');
      mockedAuthApi.getMe.mockRejectedValue(new Error('401'));

      await useAuthStore.getState().initialize();

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