import { describe, it, expect, vi, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useUserStore } from '../user'

// Mock the api module
vi.mock('@/utils/request', () => ({
  api: {
    get: vi.fn(),
    post: vi.fn(),
    put: vi.fn(),
  },
}))

import { fakeJwtWithRole, VALID_TOKEN, VALID_REFRESH_TOKEN } from '@/test/helpers'

describe('useUserStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
    localStorage.clear()
    vi.restoreAllMocks()
  })

  describe('initial state', () => {
    it('starts with null values when localStorage is empty', () => {
      const store = useUserStore()

      expect(store.token).toBeNull()
      expect(store.refreshToken).toBeNull()
      expect(store.user).toBeNull()
      expect(store.isLoggedIn).toBe(false)
      expect(store.username).toBe('')
    })

    it('restores token from localStorage', () => {
      localStorage.setItem('access_token', VALID_TOKEN)
      localStorage.setItem('refresh_token', VALID_REFRESH_TOKEN)

      const store = useUserStore()

      expect(store.token).toBe(VALID_TOKEN)
      expect(store.refreshToken).toBe(VALID_REFRESH_TOKEN)
      expect(store.isLoggedIn).toBe(true)
    })

    it('restores user info from localStorage (but isAdmin depends on JWT)', () => {
      const savedUser = { id: 1, username: 'admin', email: 'a@b.com', role: 'administrator', status: 1 }
      localStorage.setItem('user', JSON.stringify(savedUser))

      const store = useUserStore()

      expect(store.user).toEqual(savedUser)
      expect(store.username).toBe('admin')
      // isAdmin is false because there's no JWT token set
      expect(store.isAdmin).toBe(false)
    })

    it('handles corrupted user data in localStorage', () => {
      localStorage.setItem('user', 'invalid-json{')

      const store = useUserStore()

      expect(store.user).toBeNull()
    })
  })

  describe('setToken', () => {
    it('sets tokens in store and localStorage', () => {
      const store = useUserStore()

      store.setToken(VALID_TOKEN, VALID_REFRESH_TOKEN)

      expect(store.token).toBe(VALID_TOKEN)
      expect(store.refreshToken).toBe(VALID_REFRESH_TOKEN)
      expect(localStorage.getItem('access_token')).toBe(VALID_TOKEN)
      expect(localStorage.getItem('refresh_token')).toBe(VALID_REFRESH_TOKEN)
      expect(store.isLoggedIn).toBe(true)
    })
  })

  describe('setToken with remember=false', () => {
    it('does not persist tokens to localStorage', () => {
      const store = useUserStore()

      store.setToken(VALID_TOKEN, VALID_REFRESH_TOKEN, false)

      expect(store.token).toBe(VALID_TOKEN)
      expect(store.refreshToken).toBe(VALID_REFRESH_TOKEN)
      expect(localStorage.getItem('access_token')).toBeNull()
      expect(localStorage.getItem('refresh_token')).toBeNull()
    })

    it('clears existing user from localStorage', () => {
      localStorage.setItem('user', JSON.stringify({ id: 1, username: 'old' }))

      const store = useUserStore()
      store.setToken(VALID_TOKEN, VALID_REFRESH_TOKEN, false)

      expect(localStorage.getItem('user')).toBeNull()
    })
  })

  describe('setUser', () => {
    it('persists to localStorage when remember=true', () => {
      const store = useUserStore()
      store.setToken(VALID_TOKEN, VALID_REFRESH_TOKEN, true)
      const userInfo = { id: 1, username: 'test', email: 't@t.com', role: 'user', status: 1 }

      store.setUser(userInfo)

      expect(store.user).toEqual(userInfo)
      expect(store.username).toBe('test')
      expect(store.isAdmin).toBe(false)
      expect(JSON.parse(localStorage.getItem('user')!)).toEqual(userInfo)
    })

    it('does not persist to localStorage by default (remember=false)', () => {
      const store = useUserStore()
      const userInfo = { id: 1, username: 'test', email: 't@t.com', role: 'user', status: 1 }

      store.setUser(userInfo)

      expect(store.user).toEqual(userInfo)
      expect(localStorage.getItem('user')).toBeNull()
    })

    it('does not write to localStorage after setToken(remember=false)', () => {
      const store = useUserStore()
      store.setToken(VALID_TOKEN, VALID_REFRESH_TOKEN, false)

      const userInfo = { id: 1, username: 'test', email: 't@t.com', role: 'user', status: 1 }
      store.setUser(userInfo)

      expect(store.user).toEqual(userInfo)
      expect(localStorage.getItem('user')).toBeNull()
    })
  })

  describe('logout', () => {
    it('clears all state and localStorage', () => {
      const store = useUserStore()
      store.setToken(VALID_TOKEN, VALID_REFRESH_TOKEN)
      store.setUser({ id: 1, username: 'a', email: 'a@a.com', role: 'administrator', status: 1 })

      store.logout()

      expect(store.token).toBeNull()
      expect(store.refreshToken).toBeNull()
      expect(store.user).toBeNull()
      expect(store.isLoggedIn).toBe(false)
      expect(localStorage.getItem('access_token')).toBeNull()
      expect(localStorage.getItem('refresh_token')).toBeNull()
      expect(localStorage.getItem('user')).toBeNull()
    })

    it('resets _remember so subsequent setUser does not persist', () => {
      const store = useUserStore()
      store.setToken(VALID_TOKEN, VALID_REFRESH_TOKEN, true)
      store.setUser({ id: 1, username: 'a', email: 'a@a.com', role: 'administrator', status: 1 })
      expect(localStorage.getItem('user')).not.toBeNull()

      store.logout()
      store.setUser({ id: 2, username: 'b', email: 'b@b.com', role: 'user', status: 1 })

      expect(store.user?.username).toBe('b')
      expect(localStorage.getItem('user')).toBeNull()
    })
  })

  describe('fetchCurrentUser', () => {
    it('fetches user from API and updates store', async () => {
      const { api } = await import('@/utils/request')
      const mockUser = { id: 1, username: 'fetched', email: 'f@f.com', role: 'administrator', status: 1 }
      vi.mocked(api.get).mockResolvedValueOnce(mockUser)

      const store = useUserStore()
      // Set a JWT token with admin role so isAdmin works
      store.setToken(fakeJwtWithRole('administrator'), 'rt')
      const result = await store.fetchCurrentUser()

      expect(api.get).toHaveBeenCalledWith('/api/v1/me')
      expect(result).toEqual(mockUser)
      expect(store.user).toEqual(mockUser)
      // isAdmin is now derived from JWT, not user.role
      expect(store.isAdmin).toBe(true)
    })
  })

  describe('updateProfile', () => {
    it('calls API and updates local state', async () => {
      const { api } = await import('@/utils/request')
      const updatedUser = { id: 1, username: 'u', email: 'new@e.com', role: 'user', status: 1 }
      vi.mocked(api.put).mockResolvedValueOnce(updatedUser)

      const store = useUserStore()
      store.setToken(VALID_TOKEN, VALID_REFRESH_TOKEN, true)
      const result = await store.updateProfile({ email: 'new@e.com' })

      expect(api.put).toHaveBeenCalledWith('/api/v1/me/profile', { email: 'new@e.com' })
      expect(result).toEqual(updatedUser)
      expect(store.user).toEqual(updatedUser)
      expect(JSON.parse(localStorage.getItem('user')!)).toEqual(updatedUser)
    })

    it('does not update state on API error', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.put).mockRejectedValueOnce(new Error('fail'))

      const store = useUserStore()
      const originalUser = { id: 1, username: 'u', email: 'old@e.com', role: 'user', status: 1 }
      store.setUser(originalUser)

      await expect(store.updateProfile({ email: 'new@e.com' })).rejects.toThrow('fail')
      expect(store.user).toEqual(originalUser)
    })
  })

  describe('changePassword', () => {
    it('calls API and triggers logout on success', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.put).mockResolvedValueOnce(undefined)

      const store = useUserStore()
      store.setToken(VALID_TOKEN, VALID_REFRESH_TOKEN, true)
      store.setUser({ id: 1, username: 'u', email: 'u@e.com', role: 'user', status: 1 })

      await store.changePassword({
        old_password: 'old123',
        new_password: 'new123',
        new_password_confirmation: 'new123',
      })

      expect(api.put).toHaveBeenCalledWith('/api/v1/me/password', {
        old_password: 'old123',
        new_password: 'new123',
        new_password_confirmation: 'new123',
      })
      expect(store.token).toBeNull()
      expect(store.user).toBeNull()
      expect(localStorage.getItem('access_token')).toBeNull()
    })

    it('does not logout on API error', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.put).mockRejectedValueOnce(new Error('旧密码错误'))

      const store = useUserStore()
      store.setToken(VALID_TOKEN, VALID_REFRESH_TOKEN, true)
      store.setUser({ id: 1, username: 'u', email: 'u@e.com', role: 'user', status: 1 })

      await expect(store.changePassword({
        old_password: 'wrong',
        new_password: 'new123',
        new_password_confirmation: 'new123',
      })).rejects.toThrow('旧密码错误')

      expect(store.token).toBe(VALID_TOKEN)
      expect(store.user).not.toBeNull()
    })
  })

  describe('JWT-based role and isAdmin', () => {
    it('role computed derives from JWT token', () => {
      const store = useUserStore()

      store.setToken(fakeJwtWithRole('administrator'), VALID_REFRESH_TOKEN)
      expect(store.isAdmin).toBe(true)

      store.setToken(fakeJwtWithRole('accessor'), VALID_REFRESH_TOKEN)
      expect(store.isAdmin).toBe(false)
    })

    it('isAdmin is false when token is null', () => {
      const store = useUserStore()
      expect(store.isAdmin).toBe(false)
    })

    it('localStorage user role tampering does not affect isAdmin', () => {
      // Simulate attacker: localStorage user says administrator, but JWT says accessor
      const savedUser = { id: 1, username: 'hacker', email: 'h@h.com', role: 'administrator', status: 1 }
      localStorage.setItem('user', JSON.stringify(savedUser))
      localStorage.setItem('access_token', fakeJwtWithRole('accessor'))
      localStorage.setItem('refresh_token', 'rt')

      const store = useUserStore()

      // user.role says administrator (from localStorage)
      expect(store.user?.role).toBe('administrator')
      // but isAdmin is false (from JWT which says accessor)
      expect(store.isAdmin).toBe(false)
    })

    it('setToken updates role and isAdmin reactively', () => {
      const store = useUserStore()

      store.setToken(fakeJwtWithRole('accessor'), 'rt')
      expect(store.isAdmin).toBe(false)

      store.setToken(fakeJwtWithRole('administrator'), 'rt')
      expect(store.isAdmin).toBe(true)
    })

    it('logout resets role to null and isAdmin to false', () => {
      const store = useUserStore()
      store.setToken(fakeJwtWithRole('administrator'), 'rt')
      expect(store.isAdmin).toBe(true)

      store.logout()
      expect(store.isAdmin).toBe(false)
    })
  })
})
