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

const mockLogoutUser = vi.fn()

vi.mock('@/stores/auth', () => ({
  logoutUser: () => mockLogoutUser(),
}))

function makeLocalStorageStub() {
  const store: Record<string, string> = {}
  return {
    getItem: (key: string) => store[key] ?? null,
    setItem: (key: string, value: string) => {
      store[key] = value
    },
    removeItem: (key: string) => {
      delete store[key]
    },
    clear: () => {
      for (const k of Object.keys(store)) delete store[k]
    },
    _store: store,
  }
}

const localStorageStub = makeLocalStorageStub()

describe('installUnauthorizedFetchInterceptor', () => {
  const originalFetch = globalThis.fetch

  beforeEach(() => {
    vi.resetModules()
    mockLogoutUser.mockReset()
    globalThis.fetch = vi.fn()
    localStorageStub.clear()
    vi.stubGlobal('localStorage', localStorageStub)
  })

  afterEach(() => {
    globalThis.fetch = originalFetch
    vi.unstubAllGlobals()
  })

  it('attempts refresh on 401, retries the original request on success', async () => {
    const fetchMock = globalThis.fetch as unknown as ReturnType<typeof vi.fn>
    // First call: original request → 401
    // Second call: refresh → 200 (success)
    // Third call: retry original → 200
    fetchMock
      .mockResolvedValueOnce({ status: 401 } as Response)
      .mockResolvedValueOnce({ ok: true, status: 200 } as Response)
      .mockResolvedValueOnce({ status: 200, body: 'retried' } as unknown as Response)

    window.history.pushState({}, '', '/parent/profile')
    const redirectSpy = vi.fn()

    const {
      installUnauthorizedFetchInterceptor,
      setUnauthorizedRedirectHandlerForTests,
      resetInterceptorStateForTests,
    } = await import('../api')
    resetInterceptorStateForTests()
    setUnauthorizedRedirectHandlerForTests(redirectSpy)
    installUnauthorizedFetchInterceptor()

    const result = await fetch('/api/user/profile')

    // Should NOT have logged out
    expect(mockLogoutUser).not.toHaveBeenCalled()
    expect(redirectSpy).not.toHaveBeenCalled()
    // Should have called fetch 3 times: original, refresh, retry
    expect(fetchMock).toHaveBeenCalledTimes(3)
    expect(fetchMock).toHaveBeenNthCalledWith(2, '/api/auth/refresh', { method: 'POST' })
    expect((result as Response).status).toBe(200)
  })

  it('logs out when refresh also fails with 401', async () => {
    const fetchMock = globalThis.fetch as unknown as ReturnType<typeof vi.fn>
    // First call: original → 401
    // Second call: refresh → 401 (fail)
    fetchMock
      .mockResolvedValueOnce({ status: 401 } as Response)
      .mockResolvedValueOnce({ ok: false, status: 401 } as Response)

    window.history.pushState({}, '', '/parent/profile')
    const redirectSpy = vi.fn()

    const {
      installUnauthorizedFetchInterceptor,
      setUnauthorizedRedirectHandlerForTests,
      resetInterceptorStateForTests,
    } = await import('../api')
    resetInterceptorStateForTests()
    setUnauthorizedRedirectHandlerForTests(redirectSpy)
    installUnauthorizedFetchInterceptor()

    await fetch('/api/user/profile')

    expect(mockLogoutUser).toHaveBeenCalledTimes(1)
    expect(redirectSpy).toHaveBeenCalledTimes(1)
  })

  it('does not attempt refresh for auth endpoint 401s', async () => {
    const fetchMock = globalThis.fetch as unknown as ReturnType<typeof vi.fn>
    fetchMock.mockResolvedValue({ status: 401 } as Response)

    window.history.pushState({}, '', '/parent/profile')
    const redirectSpy = vi.fn()

    const {
      installUnauthorizedFetchInterceptor,
      setUnauthorizedRedirectHandlerForTests,
      resetInterceptorStateForTests,
    } = await import('../api')
    resetInterceptorStateForTests()
    setUnauthorizedRedirectHandlerForTests(redirectSpy)
    installUnauthorizedFetchInterceptor()

    await fetch('/api/auth/refresh', { method: 'POST' })

    // Should log out immediately without attempting refresh
    expect(mockLogoutUser).toHaveBeenCalledTimes(1)
    // Only 1 fetch call — no refresh attempt
    expect(fetchMock).toHaveBeenCalledTimes(1)
  })

  it('does not redirect when already on auth route', async () => {
    const fetchMock = globalThis.fetch as unknown as ReturnType<typeof vi.fn>
    // original → 401, refresh → fail
    fetchMock
      .mockResolvedValueOnce({ status: 401 } as Response)
      .mockResolvedValueOnce({ ok: false, status: 401 } as Response)

    window.history.pushState({}, '', '/auth/login')
    const redirectSpy = vi.fn()

    const {
      installUnauthorizedFetchInterceptor,
      setUnauthorizedRedirectHandlerForTests,
      resetInterceptorStateForTests,
    } = await import('../api')
    resetInterceptorStateForTests()
    setUnauthorizedRedirectHandlerForTests(redirectSpy)
    installUnauthorizedFetchInterceptor()

    await fetch('/api/auth/me')

    expect(mockLogoutUser).toHaveBeenCalledTimes(1)
    expect(redirectSpy).not.toHaveBeenCalled()
  })

  it('only makes one refresh call for concurrent 401 responses', async () => {
    const fetchMock = globalThis.fetch as unknown as ReturnType<typeof vi.fn>
    // Both original calls → 401, then one refresh → 200, then both retries → 200
    fetchMock
      .mockResolvedValueOnce({ status: 401 } as Response)
      .mockResolvedValueOnce({ status: 401 } as Response)
      .mockResolvedValueOnce({ ok: true, status: 200 } as Response) // refresh
      .mockResolvedValueOnce({ status: 200 } as Response) // retry 1
      .mockResolvedValueOnce({ status: 200 } as Response) // retry 2

    window.history.pushState({}, '', '/parent/tasks')
    const redirectSpy = vi.fn()

    const {
      installUnauthorizedFetchInterceptor,
      setUnauthorizedRedirectHandlerForTests,
      resetInterceptorStateForTests,
    } = await import('../api')
    resetInterceptorStateForTests()
    setUnauthorizedRedirectHandlerForTests(redirectSpy)
    installUnauthorizedFetchInterceptor()

    const [r1, r2] = await Promise.all([
      fetch('/api/task/add', { method: 'PUT' }),
      fetch('/api/image/list?type=2'),
    ])

    expect(mockLogoutUser).not.toHaveBeenCalled()
    expect(redirectSpy).not.toHaveBeenCalled()
    expect((r1 as Response).status).toBe(200)
    expect((r2 as Response).status).toBe(200)
  })

  it('does not log out for non-401 responses', async () => {
    const fetchMock = globalThis.fetch as unknown as ReturnType<typeof vi.fn>
    fetchMock.mockResolvedValue({ status: 200 } as Response)

    window.history.pushState({}, '', '/parent')
    const redirectSpy = vi.fn()

    const {
      installUnauthorizedFetchInterceptor,
      setUnauthorizedRedirectHandlerForTests,
      resetInterceptorStateForTests,
    } = await import('../api')
    resetInterceptorStateForTests()
    setUnauthorizedRedirectHandlerForTests(redirectSpy)
    installUnauthorizedFetchInterceptor()

    await fetch('/api/child/list')

    expect(mockLogoutUser).not.toHaveBeenCalled()
    expect(redirectSpy).not.toHaveBeenCalled()
  })

  it('sets lastRefreshAt in localStorage after a successful refresh', async () => {
    const fetchMock = globalThis.fetch as unknown as ReturnType<typeof vi.fn>
    fetchMock
      .mockResolvedValueOnce({ status: 401 } as Response)
      .mockResolvedValueOnce({ ok: true, status: 200 } as Response)
      .mockResolvedValueOnce({ status: 200 } as Response)

    window.history.pushState({}, '', '/parent')
    const redirectSpy = vi.fn()

    const {
      installUnauthorizedFetchInterceptor,
      setUnauthorizedRedirectHandlerForTests,
      resetInterceptorStateForTests,
    } = await import('../api')
    resetInterceptorStateForTests()
    setUnauthorizedRedirectHandlerForTests(redirectSpy)
    installUnauthorizedFetchInterceptor()

    await fetch('/api/user/profile')

    const lastRefresh = localStorageStub.getItem('lastRefreshAt')
    expect(lastRefresh).not.toBeNull()
    expect(Number(lastRefresh)).toBeLessThanOrEqual(Date.now())
    expect(mockLogoutUser).not.toHaveBeenCalled()
    expect(redirectSpy).not.toHaveBeenCalled()
  })

  it('skips refresh call when another tab recently refreshed', async () => {
    const fetchMock = globalThis.fetch as unknown as ReturnType<typeof vi.fn>
    // Only original request and retry; refresh should be skipped due to cross-tab coordination
    fetchMock
      .mockResolvedValueOnce({ status: 401 } as Response)
      .mockResolvedValueOnce({ status: 200, body: 'retried' } as unknown as Response)

    window.history.pushState({}, '', '/parent')
    const redirectSpy = vi.fn()

    const {
      installUnauthorizedFetchInterceptor,
      setUnauthorizedRedirectHandlerForTests,
      resetInterceptorStateForTests,
    } = await import('../api')
    resetInterceptorStateForTests()
    setUnauthorizedRedirectHandlerForTests(redirectSpy)
    installUnauthorizedFetchInterceptor()

    // Simulate another tab having refreshed 1 second ago, after resetInterceptorStateForTests
    localStorageStub.setItem('lastRefreshAt', String(Date.now() - 1000))

    const result = await fetch('/api/user/profile')

    // Should not call /api/auth/refresh; only original + retry
    expect(fetchMock).toHaveBeenCalledTimes(2)
    expect(fetchMock.mock.calls.map((c) => c[0])).toEqual([
      '/api/user/profile',
      '/api/user/profile',
    ])
    expect(mockLogoutUser).not.toHaveBeenCalled()
    expect(redirectSpy).not.toHaveBeenCalled()
    expect((result as Response).status).toBe(200)
  })
})
