import { setActivePinia, createPinia } from 'pinia';
import { useAuthStore, type AuthState } from '../auth'; // Adjust path if necessary
import { apiClient } from '@/services/api';
import router from '@/router';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { computed } from 'vue';

// Mock localStorage
const localStorageMock = (() => {
  let store: Record<string, string> = {};
  return {
    getItem: (key: string) => store[key] || null,
    setItem: (key: string, value: string) => {
      store[key] = value.toString();
    },
    removeItem: (key: string) => {
      delete store[key];
    },
    clear: () => {
      store = {};
    },
  };
})();

Object.defineProperty(window, 'localStorage', { value: localStorageMock });

// Mock apiClient
vi.mock('@/services/api', () => ({
  apiClient: {
    get: vi.fn(),
    post: vi.fn(),
  },
}));

// Mock router
vi.mock('@/router', () => ({
  default: {
    push: vi.fn(),
  },
}));


describe('Auth Store', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
    localStorageMock.clear();
    vi.clearAllMocks(); // Clear all mocks before each test
  });

  describe('Initial State', () => {
    it('initializes with null tokens and user when localStorage is empty', () => {
      const authStore = useAuthStore();
      expect(authStore.accessToken).toBeNull();
      expect(authStore.refreshToken).toBeNull();
      expect(authStore.user).toBeNull();
      expect(authStore.isAuthenticated).toBe(false);
    });

    it('initializes tokens from localStorage if present', () => {
      localStorageMock.setItem('token', 'test-access-token');
      localStorageMock.setItem('refreshToken', 'test-refresh-token');
      const authStore = useAuthStore();
      expect(authStore.accessToken).toBe('test-access-token');
      expect(authStore.refreshToken).toBe('test-refresh-token');
      expect(authStore.isAuthenticated).toBe(true);
    });
  });

  describe('Getters', () => {
    it('isAuthenticated returns true if accessToken exists', () => {
      const authStore = useAuthStore();
      authStore.accessToken = 'some-token';
      expect(authStore.isAuthenticated).toBe(true);
    });

    it('isAuthenticated returns false if accessToken is null', () => {
      const authStore = useAuthStore();
      authStore.accessToken = null;
      expect(authStore.isAuthenticated).toBe(false);
    });

    it('getUser returns the current user', () => {
      const authStore = useAuthStore();
      const testUser = { email: 'test@example.com', name: 'Test User' };
      authStore.user = testUser;
      expect(authStore.getUser).toEqual(testUser);
    });
  });

  describe('Actions', () => {
    const testTokens = { access_token: 'new-access-token', refresh_token: 'new-refresh-token' };
    const testUser = { id: 1, email: 'user@example.com', name: 'User Name' };

    it('setTokens correctly updates state and localStorage', () => {
      const authStore = useAuthStore();
      authStore.setTokens(testTokens);
      expect(authStore.accessToken).toBe(testTokens.access_token);
      expect(authStore.refreshToken).toBe(testTokens.refresh_token);
      expect(localStorageMock.getItem('token')).toBe(testTokens.access_token);
      expect(localStorageMock.getItem('refreshToken')).toBe(testTokens.refresh_token);
    });
    
    it('setTokens handles missing refresh_token', () => {
        const authStore = useAuthStore();
        const accessTokenOnly = { access_token: 'access-only-token' };
        authStore.setTokens(accessTokenOnly);
        expect(authStore.accessToken).toBe(accessTokenOnly.access_token);
        expect(authStore.refreshToken).toBeNull(); // Assuming it was null before
        expect(localStorageMock.getItem('token')).toBe(accessTokenOnly.access_token);
        expect(localStorageMock.getItem('refreshToken')).toBeNull();
      });

    it('clearTokens correctly clears state and localStorage', () => {
      const authStore = useAuthStore();
      // Set some initial values
      authStore.setTokens(testTokens);
      authStore.user = testUser;

      authStore.clearTokens();

      expect(authStore.accessToken).toBeNull();
      expect(authStore.refreshToken).toBeNull();
      expect(authStore.user).toBeNull();
      expect(localStorageMock.getItem('token')).toBeNull();
      expect(localStorageMock.getItem('refreshToken')).toBeNull();
    });

    it('setUser correctly updates the user state', () => {
      const authStore = useAuthStore();
      authStore.setUser(testUser);
      expect(authStore.user).toEqual(testUser);
    });

    describe('fetchCurrentUser', () => {
      it('clears tokens and returns null if no accessToken exists', async () => {
        const authStore = useAuthStore();
        authStore.accessToken = null; // Ensure no token
        const result = await authStore.fetchCurrentUser();
        expect(result).toBeNull();
        expect(authStore.user).toBeNull();
        expect(apiClient.get).not.toHaveBeenCalled();
      });

      it('fetches and sets user data on successful API call', async () => {
        const authStore = useAuthStore();
        authStore.accessToken = 'valid-token';
        (apiClient.get as vi.Mock).mockResolvedValue({ data: testUser });

        const result = await authStore.fetchCurrentUser();

        expect(apiClient.get).toHaveBeenCalledWith('/users/me/');
        expect(result).toEqual(testUser);
        expect(authStore.user).toEqual(testUser);
      });

      it('clears tokens and user on failed API call', async () => {
        const authStore = useAuthStore();
        authStore.accessToken = 'valid-token';
        (apiClient.get as vi.Mock).mockRejectedValue(new Error('API Error'));

        const result = await authStore.fetchCurrentUser();

        expect(apiClient.get).toHaveBeenCalledWith('/users/me/');
        expect(result).toBeNull();
        expect(authStore.user).toBeNull();
        expect(authStore.accessToken).toBeNull(); // Because clearTokens is called
        expect(localStorageMock.getItem('token')).toBeNull();
      });
    });

    describe('login', () => {
      const loginCredentials = { email: 'test@example.com', password: 'password' };
      const loginResponseTokens = { access_token: 'login-access', refresh_token: 'login-refresh' };
      const profileData = { id: 'user1', email: loginCredentials.email, name: 'Test User' };

      it('calls login API, sets tokens, fetches user, and updates state on success', async () => {
        const authStore = useAuthStore();
        (apiClient.post as vi.Mock).mockResolvedValue({ data: loginResponseTokens });
        (apiClient.get as vi.Mock).mockResolvedValue({ data: profileData }); // For fetchCurrentUser

        await authStore.login(loginCredentials.email, loginCredentials.password);

        const expectedFormData = new FormData();
        expectedFormData.append('username', loginCredentials.email);
        expectedFormData.append('password', loginCredentials.password);

        expect(apiClient.post).toHaveBeenCalledWith('/auth/login/', expectedFormData, {
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        });
        expect(authStore.accessToken).toBe(loginResponseTokens.access_token);
        expect(authStore.refreshToken).toBe(loginResponseTokens.refresh_token);
        expect(localStorageMock.getItem('token')).toBe(loginResponseTokens.access_token);
        expect(apiClient.get).toHaveBeenCalledWith('/users/me/');
        expect(authStore.user).toEqual(profileData);
      });

      it('does not set tokens or user on login API failure', async () => {
        const authStore = useAuthStore();
        (apiClient.post as vi.Mock).mockRejectedValue(new Error('Login failed'));

        await expect(authStore.login(loginCredentials.email, loginCredentials.password))
          .rejects.toThrow('Login failed');

        expect(authStore.accessToken).toBeNull();
        expect(authStore.refreshToken).toBeNull();
        expect(authStore.user).toBeNull();
        expect(apiClient.get).not.toHaveBeenCalled(); // fetchCurrentUser should not be called
      });
    });

    describe('signup', () => {
      const signupData = { name: 'New User', email: 'new@example.com', password: 'newpassword123' };
      const signupApiResponse = { id: 'newUser123', message: 'Signup successful' };

      it('calls signup API with correct data and returns response', async () => {
        const authStore = useAuthStore();
        (apiClient.post as vi.Mock).mockResolvedValue({ data: signupApiResponse });

        const response = await authStore.signup(signupData);

        expect(apiClient.post).toHaveBeenCalledWith('/auth/signup/', signupData);
        expect(response).toEqual(signupApiResponse);
        // Current signup action doesn't auto-login or set user, so check no side-effects on auth state
        expect(authStore.accessToken).toBeNull();
        expect(authStore.user).toBeNull();
      });

      it('propagates error on signup API failure', async () => {
        const authStore = useAuthStore();
        (apiClient.post as vi.Mock).mockRejectedValue(new Error('Signup failed'));

        await expect(authStore.signup(signupData))
          .rejects.toThrow('Signup failed');
        
        expect(authStore.accessToken).toBeNull();
        expect(authStore.user).toBeNull();
      });
    });

    describe('logout', () => {
      it('clears tokens, user, and redirects to login', async () => {
        const authStore = useAuthStore();
        // Setup initial logged-in state
        authStore.setTokens(testTokens);
        authStore.setUser(testUser);

        await authStore.logout();

        expect(authStore.accessToken).toBeNull();
        expect(authStore.refreshToken).toBeNull();
        expect(authStore.user).toBeNull();
        expect(localStorageMock.getItem('token')).toBeNull();
        expect(localStorageMock.getItem('refreshToken')).toBeNull();
        expect(router.push).toHaveBeenCalledWith('/auth/login');
      });
    });
  });
});
