import axios, { type AxiosInstance, type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios';
import { apiClient, api as configuredApiInstance, API_ENDPOINTS } from '../api'; // Adjust path
import { API_BASE_URL as MOCK_API_BASE_URL } from '@/config/api-config';
import { useAuthStore } from '@/stores/auth';
import router from '@/router';

// --- Mocks ---
vi.mock('axios', async (importOriginal) => {
  const actualAxios = await importOriginal<typeof axios>();
  return {
    ...actualAxios, // Spread actual axios to get CancelToken, isCancel, etc.
    create: vi.fn(), // Mock axios.create
    // Mocking static methods if needed by the module under test, though not directly by api.ts
    // get: vi.fn(),
    // post: vi.fn(),
  };
});

vi.mock('@/stores/auth', () => ({
  useAuthStore: vi.fn(() => ({
    refreshToken: null,
    setTokens: vi.fn(),
    clearTokens: vi.fn(),
  })),
}));

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

// 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 });


// --- Test Suite ---
describe('API Service (api.ts)', () => {
  let mockAxiosInstance: Partial<AxiosInstance>;
  let requestInterceptor: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
  let responseInterceptorSuccess: (response: any) => any;
  let responseInterceptorError: (error: any) => Promise<any>;
  let mockAuthStore: ReturnType<typeof useAuthStore>;

  beforeEach(() => {
    // Reset all mocks
    vi.clearAllMocks();
    localStorageMock.clear();

    // Setup mock axios instance that axios.create will return
    mockAxiosInstance = {
      interceptors: {
        request: { use: vi.fn() },
        response: {
          use: vi.fn((successCallback, errorCallback) => {
            // Store the callbacks so we can call them in tests
            _responseInterceptorSuccess = successCallback;
            _responseInterceptorError = errorCallback;
          })
        },
      },
      get: vi.fn(),
      post: vi.fn(),
      put: vi.fn(),
      delete: vi.fn(),
    };
    (axios.create as vi.Mock).mockReturnValue(mockAxiosInstance);

    // Re-evaluate api.ts by importing it or re-triggering its setup
    // This is tricky as modules are cached. For simplicity, we assume the interceptors
    // are captured correctly when api.ts was first imported by the test runner.
    // The configuredApiInstance is the one created in api.ts
    // We need to ensure our mockAxiosInstance is what it uses.
    // The `api` export from `../api` is the axios instance.
    // We can't easily swap it post-import if api.ts doesn't export a factory.
    // However, by mocking axios.create before api.ts is first processed by Jest/Vitest,
    // configuredApiInstance *should* be our mockAxiosInstance.

    mockAuthStore = useAuthStore();
    (useAuthStore as vi.Mock).mockReturnValue(mockAuthStore); // Ensure this instance is used

    // Manually call the interceptor setup functions from api.ts if they were exported
    // Or, rely on the fact that they were called when api.ts was imported.
    // The interceptors are set up on `configuredApiInstance` which should be `mockAxiosInstance`.
    // So, `requestInterceptor` and `responseInterceptorError` should be populated.
    // This part is a bit of a dance with module imports.
    // To be absolutely sure, `api.ts` could export its setup functions or be more DI-friendly.
    // For now, we assume the interceptors on `mockAxiosInstance` got registered.
  });

  describe('Axios Instance Configuration', () => {
    it('should create an axios instance with correct baseURL and default headers', () => {
      // This test relies on axios.create being called when api.ts is imported.
      // The call to axios.create() happens when api.ts is loaded.
      expect(axios.create).toHaveBeenCalledWith({
        baseURL: MOCK_API_BASE_URL,
        headers: { 'Content-Type': 'application/json' },
        withCredentials: true,
      });
    });
  });

  describe('Request Interceptor', () => {
    it('should add Authorization header if token exists in localStorage', () => {
      localStorageMock.setItem('token', 'test-token');
      const config: InternalAxiosRequestConfig = { headers: {} } as InternalAxiosRequestConfig;

      // configuredApiInstance is the instance from api.ts, which should have the interceptor
      // We need to ensure our mockAxiosInstance.interceptors.request.use captured the callback
      // Then we call it manually.
      const processedConfig = requestInterceptor(config);

      expect(processedConfig.headers.Authorization).toBe('Bearer test-token');
    });

    it('should not add Authorization header if token does not exist', () => {
      const config: InternalAxiosRequestConfig = { headers: {} } as InternalAxiosRequestConfig;
      const processedConfig = requestInterceptor(config);
      expect(processedConfig.headers.Authorization).toBeUndefined();
    });
  });

  describe('Response Interceptor (401 Refresh Logic)', () => {
    const originalRequestConfig: InternalAxiosRequestConfig = { headers: {}, _retry: false } as InternalAxiosRequestConfig;

    it('successful token refresh and retry', async () => {
      localStorageMock.setItem('token', 'old-token'); // For the initial failed request
      mockAuthStore.refreshToken = 'valid-refresh-token';
      const error = { config: originalRequestConfig, response: { status: 401 } };

      (mockAxiosInstance.post as vi.Mock).mockResolvedValueOnce({ // for refresh call
        data: { access_token: 'new-access-token', refresh_token: 'new-refresh-token' },
      });
      (mockAxiosInstance as any).mockResolvedValueOnce({ data: 'retried request data' }); // for retried original request

      const result = await responseInterceptorError(error);

      expect(mockAxiosInstance.post).toHaveBeenCalledWith('/auth/jwt/refresh', { refresh_token: 'valid-refresh-token' });
      expect(mockAuthStore.setTokens).toHaveBeenCalledWith({ access_token: 'new-access-token', refresh_token: 'new-refresh-token' });
      expect(originalRequestConfig.headers.Authorization).toBe('Bearer new-access-token');
      expect(result.data).toBe('retried request data');
    });

    it('failed token refresh redirects to login', async () => {
      mockAuthStore.refreshToken = 'valid-refresh-token';
      const error = { config: originalRequestConfig, response: { status: 401 } };
      const refreshError = new Error('Refresh failed');
      (mockAxiosInstance.post as vi.Mock).mockRejectedValueOnce(refreshError); // refresh call fails

      await expect(responseInterceptorError(error)).rejects.toThrow('Refresh failed');

      expect(mockAxiosInstance.post).toHaveBeenCalledWith('/auth/jwt/refresh', { refresh_token: 'valid-refresh-token' });
      expect(mockAuthStore.clearTokens).toHaveBeenCalled();
      expect(router.push).toHaveBeenCalledWith('/auth/login');
    });

    it('no refresh token, redirects to login', async () => {
      mockAuthStore.refreshToken = null;
      const error = { config: originalRequestConfig, response: { status: 401 } };

      await expect(responseInterceptorError(error)).rejects.toEqual(error); // Original error rejected

      expect(mockAuthStore.clearTokens).toHaveBeenCalled();
      expect(router.push).toHaveBeenCalledWith('/auth/login');
      expect(mockAxiosInstance.post).not.toHaveBeenCalledWith('/auth/jwt/refresh', expect.anything());
    });

    it('should not retry if _retry is already true', async () => {
      const error = { config: { ...originalRequestConfig, _retry: true }, response: { status: 401 } };
      await expect(responseInterceptorError(error)).rejects.toEqual(error);
      expect(mockAxiosInstance.post).not.toHaveBeenCalledWith('/auth/jwt/refresh', expect.anything());
    });

    it('passes through non-401 errors', async () => {
      const error = { config: originalRequestConfig, response: { status: 500, data: 'Server Error' } };
      await expect(responseInterceptorError(error)).rejects.toEqual(error);
      expect(mockAxiosInstance.post).not.toHaveBeenCalledWith('/auth/jwt/refresh', expect.anything());
    });
  });

  describe('apiClient methods', () => {
    // Note: API_BASE_URL is part of getApiUrl, which prepends it.
    // The mockAxiosInstance is already configured with baseURL.
    // So, api.get(URL) will have URL = MOCK_API_BASE_URL + endpoint.
    // However, the apiClient methods call getApiUrl(endpoint) which results in MOCK_API_BASE_URL + endpoint.
    // This means the final URL passed to the mockAxiosInstance.get (etc.) will be effectively MOCK_API_BASE_URL + MOCK_API_BASE_URL + endpoint
    // This is a slight duplication issue in the original api.ts's apiClient if not careful.
    // For testing, we'll assume the passed endpoint to apiClient methods is relative (e.g., "/users")
    // and getApiUrl correctly forms the full path once.

    const testEndpoint = '/test'; // Example, will be combined with API_ENDPOINTS
    const fullTestEndpoint = MOCK_API_BASE_URL + API_ENDPOINTS.AUTH.LOGIN; // Using a concrete endpoint
    const responseData = { message: 'success' };
    const requestData = { foo: 'bar' };

    beforeEach(() => {
      // Reset mockAxiosInstance calls for each apiClient method test
      (mockAxiosInstance.get as vi.Mock).mockClear();
      (mockAxiosInstance.post as vi.Mock).mockClear();
      (mockAxiosInstance.put as vi.Mock).mockClear();
      (mockAxiosInstance.patch as vi.Mock).mockClear();
      (mockAxiosInstance.delete as vi.Mock).mockClear();
    });

    it('apiClient.get calls configuredApiInstance.get with full URL', async () => {
      (mockAxiosInstance.get as vi.Mock).mockResolvedValue({ data: responseData });
      const result = await apiClient.get(API_ENDPOINTS.AUTH.LOGIN);
      expect(mockAxiosInstance.get).toHaveBeenCalledWith(fullTestEndpoint, undefined);
      expect(result.data).toEqual(responseData);
    });

    it('apiClient.post calls configuredApiInstance.post with full URL and data', async () => {
      (mockAxiosInstance.post as vi.Mock).mockResolvedValue({ data: responseData });
      const result = await apiClient.post(API_ENDPOINTS.AUTH.LOGIN, requestData);
      expect(mockAxiosInstance.post).toHaveBeenCalledWith(fullTestEndpoint, requestData, undefined);
      expect(result.data).toEqual(responseData);
    });

    it('apiClient.put calls configuredApiInstance.put with full URL and data', async () => {
      (mockAxiosInstance.put as vi.Mock).mockResolvedValue({ data: responseData });
      const result = await apiClient.put(API_ENDPOINTS.AUTH.LOGIN, requestData);
      expect(mockAxiosInstance.put).toHaveBeenCalledWith(fullTestEndpoint, requestData, undefined);
      expect(result.data).toEqual(responseData);
    });

    it('apiClient.patch calls configuredApiInstance.patch with full URL and data', async () => {
      (mockAxiosInstance.patch as vi.Mock).mockResolvedValue({ data: responseData });
      const result = await apiClient.patch(API_ENDPOINTS.AUTH.LOGIN, requestData);
      expect(mockAxiosInstance.patch).toHaveBeenCalledWith(fullTestEndpoint, requestData, undefined);
      expect(result.data).toEqual(responseData);
    });

    it('apiClient.delete calls configuredApiInstance.delete with full URL', async () => {
      (mockAxiosInstance.delete as vi.Mock).mockResolvedValue({ data: responseData });
      const result = await apiClient.delete(API_ENDPOINTS.AUTH.LOGIN);
      expect(mockAxiosInstance.delete).toHaveBeenCalledWith(fullTestEndpoint, undefined);
      expect(result.data).toEqual(responseData);
    });

    it('apiClient.get propagates errors', async () => {
      const error = new Error('Network Error');
      (mockAxiosInstance.get as vi.Mock).mockRejectedValue(error);
      await expect(apiClient.get(API_ENDPOINTS.AUTH.LOGIN)).rejects.toThrow('Network Error');
    });
  });

  describe('Interceptor Registration on Actual Instance', () => {
    // This is more of an integration test for the interceptor setup itself.
    it('should have registered interceptors on the true configuredApiInstance', () => {
      // configuredApiInstance is the actual instance from api.ts
      // We check if its interceptors.request.use was called (which our mock does)
      // This relies on the mockAxiosInstance being the one that was used.
      expect(mockAxiosInstance.interceptors?.request.use).toHaveBeenCalled();
      expect(mockAxiosInstance.interceptors?.response.use).toHaveBeenCalled();
    });
  });

  describe('Error handling with refresh logic', () => {
    it('should attempt token refresh on 401 error', async () => {
      const _testEndpoint = '/test'; // Example, will be combined with API_ENDPOINTS
    });
  });

});

// Mock actual config values
vi.mock('@/config/api-config', () => ({
  API_BASE_URL: 'http://mockapi.com/api/v1',
  API_VERSION: 'v1',
  API_ENDPOINTS: {
    AUTH: {
      LOGIN: '/auth/login/',
      SIGNUP: '/auth/signup/',
      REFRESH: '/auth/jwt/refresh/',
      LOGOUT: '/auth/logout/',
    },
    USERS: {
      PROFILE: '/users/me/',
      // Add other user-related endpoints here
    },
    LISTS: {
      BASE: '/lists/',
      BY_ID: (id: string | number) => `/lists/${id}/`,
      ITEMS: (listId: string | number) => `/lists/${listId}/items/`,
      ITEM: (listId: string | number, itemId: string | number) => `/lists/${listId}/items/${itemId}/`,
    }
    // Add other resource endpoints here
  },
}));
