/**
 * T-064 — axiosClient interceptor tests (MSW v2)
 *
 * Tests cover:
 * 1. request_includesBearer_whenAccessTokenPresent
 * 2. request_noBearer_whenAccessTokenNull
 * 3. response_401_triggersRefreshAndRetries
 * 4. response_401_threeParallel_singleRefresh (CRITICAL — singleton promise)
 * 5. response_401_refreshFails_clearsAuthAndRejects
 * 6. response_401_onLoginUrl_noRefresh
 * 7. response_401_onRefreshUrl_noLoop
 */

import { describe, it, expect, beforeAll, afterAll, afterEach, beforeEach, vi } from 'vitest'
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { useAuthStore } from '../../stores/authStore'

const API_URL = 'http://localhost:5000'

// Helper to reset the refreshPromise singleton between tests
// by reimporting the module. We use a direct import instead.
async function getAxiosClient() {
  const mod = await import('../../api/axiosClient')
  return mod.axiosClient
}

// MSW server
const server = setupServer()

beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' }))
afterAll(() => server.close())

beforeEach(() => {
  // Reset store to clean slate
  useAuthStore.setState({
    user: null,
    accessToken: null,
    refreshToken: null,
    expiresAt: null,
  })
  // Reset window.location mock if applied
  vi.restoreAllMocks()
})

afterEach(() => {
  server.resetHandlers()
})

function setAuth(accessToken: string, refreshToken: string) {
  useAuthStore.setState({
    user: { id: 1, username: 'admin', nombre: 'Admin', rol: 'admin', permisos: [], mustChangePassword: false },
    accessToken,
    refreshToken,
    expiresAt: Date.now() + 3600 * 1000,
  })
}

describe('axiosClient', () => {
  describe('request interceptor', () => {
    it('request_includesBearer_whenAccessTokenPresent', async () => {
      setAuth('my-access-token', 'my-refresh-token')

      let capturedAuthHeader: string | null = null
      server.use(
        http.get(`${API_URL}/api/v1/test`, ({ request }) => {
          capturedAuthHeader = request.headers.get('Authorization')
          return HttpResponse.json({ ok: true })
        }),
      )

      const client = await getAxiosClient()
      await client.get('/api/v1/test')

      expect(capturedAuthHeader).toBe('Bearer my-access-token')
    })

    it('request_noBearer_whenAccessTokenNull', async () => {
      // No auth set — accessToken is null

      let capturedAuthHeader: string | null | undefined = undefined
      server.use(
        http.get(`${API_URL}/api/v1/test`, ({ request }) => {
          capturedAuthHeader = request.headers.get('Authorization')
          return HttpResponse.json({ ok: true })
        }),
      )

      const client = await getAxiosClient()
      await client.get('/api/v1/test')

      expect(capturedAuthHeader).toBeNull()
    })
  })

  describe('response interceptor — 401 handling', () => {
    it('response_401_triggersRefreshAndRetries', async () => {
      setAuth('expired-access', 'valid-refresh')

      let requestCount = 0
      server.use(
        // Protected endpoint: returns 401 first, then 200 after refresh
        http.get(`${API_URL}/api/v1/protected`, ({ request }) => {
          requestCount++
          const auth = request.headers.get('Authorization')
          if (auth === 'Bearer new-access-token') {
            return HttpResponse.json({ data: 'secret' })
          }
          return new HttpResponse(null, { status: 401 })
        }),
        // Refresh endpoint
        http.post(`${API_URL}/api/v1/auth/refresh`, () => {
          return HttpResponse.json({
            accessToken: 'new-access-token',
            refreshToken: 'new-refresh-token',
            expiresIn: 3600,
          })
        }),
      )

      const client = await getAxiosClient()
      const res = await client.get('/api/v1/protected')

      expect(res.data).toEqual({ data: 'secret' })
      expect(requestCount).toBe(2) // 1st: 401, 2nd: 200 with new token
      expect(useAuthStore.getState().accessToken).toBe('new-access-token')
      expect(useAuthStore.getState().refreshToken).toBe('new-refresh-token')
    })

    it('response_401_threeParallel_singleRefresh', async () => {
      setAuth('expired-access', 'valid-refresh')

      let refreshCallCount = 0
      server.use(
        http.get(`${API_URL}/api/v1/protected`, ({ request }) => {
          const auth = request.headers.get('Authorization')
          if (auth === 'Bearer new-access-from-refresh') {
            return HttpResponse.json({ data: 'ok' })
          }
          return new HttpResponse(null, { status: 401 })
        }),
        http.post(`${API_URL}/api/v1/auth/refresh`, async () => {
          refreshCallCount++
          // Simulate slight delay so concurrent requests queue up
          await new Promise((r) => setTimeout(r, 20))
          return HttpResponse.json({
            accessToken: 'new-access-from-refresh',
            refreshToken: 'new-refresh-from-refresh',
            expiresIn: 3600,
          })
        }),
      )

      const client = await getAxiosClient()

      // Fire 3 requests simultaneously — all get 401, all should wait on single refresh
      const [r1, r2, r3] = await Promise.all([
        client.get('/api/v1/protected'),
        client.get('/api/v1/protected'),
        client.get('/api/v1/protected'),
      ])

      expect(r1.data).toEqual({ data: 'ok' })
      expect(r2.data).toEqual({ data: 'ok' })
      expect(r3.data).toEqual({ data: 'ok' })

      // CRITICAL: only ONE call to /auth/refresh despite 3 parallel 401s
      expect(refreshCallCount).toBe(1)
    })

    it('response_401_refreshFails_clearsAuthAndRejects', async () => {
      setAuth('expired-access', 'invalid-refresh')

      // Mock window.location.assign to avoid jsdom navigation error
      const assignMock = vi.fn()
      Object.defineProperty(window, 'location', {
        value: { ...window.location, assign: assignMock },
        writable: true,
      })

      server.use(
        http.get(`${API_URL}/api/v1/protected`, () => {
          return new HttpResponse(null, { status: 401 })
        }),
        http.post(`${API_URL}/api/v1/auth/refresh`, () => {
          return new HttpResponse(null, { status: 401 })
        }),
      )

      const client = await getAxiosClient()

      await expect(client.get('/api/v1/protected')).rejects.toThrow()

      // Auth should be cleared
      const state = useAuthStore.getState()
      expect(state.accessToken).toBeNull()
      expect(state.user).toBeNull()
    })

    it('response_401_onLoginUrl_noRefresh', async () => {
      setAuth('expired-access', 'valid-refresh')

      let refreshCalled = false
      server.use(
        http.post(`${API_URL}/api/v1/auth/login`, () => {
          return new HttpResponse(null, { status: 401 })
        }),
        http.post(`${API_URL}/api/v1/auth/refresh`, () => {
          refreshCalled = true
          return HttpResponse.json({
            accessToken: 'new-access',
            refreshToken: 'new-refresh',
            expiresIn: 3600,
          })
        }),
      )

      const client = await getAxiosClient()

      // Login 401 should NOT trigger refresh — just reject
      await expect(client.post('/api/v1/auth/login', {})).rejects.toThrow()
      expect(refreshCalled).toBe(false)
    })

    it('response_401_onRefreshUrl_noLoop', async () => {
      setAuth('expired-access', 'valid-refresh')

      let refreshCallCount = 0
      server.use(
        http.post(`${API_URL}/api/v1/auth/refresh`, () => {
          refreshCallCount++
          return new HttpResponse(null, { status: 401 })
        }),
      )

      const client = await getAxiosClient()

      // Calling refresh endpoint that returns 401 should NOT re-trigger refresh
      await expect(
        client.post('/api/v1/auth/refresh', { accessToken: 'x', refreshToken: 'y' }),
      ).rejects.toThrow()

      // Should have called /refresh exactly once (the explicit call), no loop
      expect(refreshCallCount).toBe(1)
    })
  })
})
