import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createEvent, mockSuccessReadBody } from './setup'
import { readFileSync } from 'fs'

vi.mock('../server/utils/auth.js', () => {
  return {
    readUsers: vi.fn(),
    writeUsers: vi.fn(),
    verifyPassword: vi.fn(),
    generateToken: vi.fn(),
    createSession: vi.fn(),
    hashPassword: vi.fn(),
    verifyToken: vi.fn(),
    deleteSession: vi.fn(),
    getUserFromToken: vi.fn(),
    readSessions: vi.fn(),
    writeSessions: vi.fn(),
    migrateUserRoles: vi.fn((user) => {
      if (!user) return user
      if (Array.isArray(user.roles)) return user
      if (user.role) {
        user.roles = [user.role]
        delete user.role
      } else {
        user.roles = ['mitglied']
      }
      return user
    }),
    hasRole: vi.fn((user, role) => {
      if (!user) return false
      const roles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : [])
      return roles.includes(role)
    }),
    hasAnyRole: vi.fn((user, ...roles) => {
      if (!user) return false
      const userRoles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : [])
      return roles.some(r => userRoles.includes(r))
    }),
    hasAllRoles: vi.fn((user, ...roles) => {
      if (!user) return false
      const userRoles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : [])
      return roles.every(r => userRoles.includes(r))
    })
  }
})

vi.mock('nodemailer', () => {
  const sendMail = vi.fn().mockResolvedValue(true)
  const createTransport = vi.fn(() => ({ sendMail }))
  return {
    default: { createTransport },
    createTransport
  }
})

const authUtils = await import('../server/utils/auth.js')
const nodemailer = await import('nodemailer')

import loginHandler from '../server/api/auth/login.post.js'
import logoutHandler from '../server/api/auth/logout.post.js'
import registerHandler from '../server/api/auth/register.post.js'
import resetPasswordHandler from '../server/api/auth/reset-password.post.js'
import statusHandler from '../server/api/auth/status.get.js'
import versionHandler from '../server/api/app/version.get.js'

describe('Auth API Endpoints', () => {
  afterEach(() => {
    delete process.env.NODE_ENV
    delete process.env.APP_ENV
  })

  beforeEach(() => {
    // Setze SMTP-Credentials für Tests
    process.env.SMTP_USER = 'test@example.com'
    process.env.SMTP_PASS = 'test-password'
    vi.clearAllMocks()
  })

  describe('POST /api/auth/login', () => {
    it('wirft 400 bei fehlenden Feldern', async () => {
      const event = createEvent()
      mockSuccessReadBody({})

      await expect(loginHandler(event)).rejects.toMatchObject({ statusCode: 400 })
    })

    it('wirft 401 bei unbekanntem Benutzer', async () => {
      const event = createEvent()
      mockSuccessReadBody({ email: 'test@example.com', password: 'password' })
      authUtils.readUsers.mockResolvedValue([])

      await expect(loginHandler(event)).rejects.toMatchObject({ statusCode: 401 })
    })

    it('gibt Token und Cookie bei Erfolg zurück', async () => {
      const event = createEvent()
      const user = { id: '1', email: 'test@example.com', password: 'hash', role: 'mitglied', active: true, lastLogin: null }
      mockSuccessReadBody({ email: user.email, password: 'plain' })
      authUtils.readUsers.mockResolvedValue([user])
      authUtils.verifyPassword.mockResolvedValue(true)
      authUtils.generateToken.mockReturnValue('jwt-token')
      authUtils.createSession.mockResolvedValue({})
      authUtils.writeUsers.mockResolvedValue(true)

      const response = await loginHandler(event)

      expect(response.success).toBe(true)
      expect(response.token).toBe('jwt-token')
      expect(response.user).toMatchObject({ id: '1', email: user.email })
      expect(authUtils.createSession).toHaveBeenCalledWith('1', 'jwt-token')
      expect(authUtils.writeUsers).toHaveBeenCalled()
    })
  })

  describe('POST /api/auth/logout', () => {
    it('löscht Session und Cookie, wenn Token vorhanden ist', async () => {
      const event = createEvent({ cookies: { auth_token: 'token' } })
      authUtils.deleteSession.mockResolvedValue()

      const response = await logoutHandler(event)

      expect(response.success).toBe(true)
      expect(authUtils.deleteSession).toHaveBeenCalledWith('token')
    })

    it('wirft 500 bei Fehlern', async () => {
      const event = createEvent({ cookies: { auth_token: 'token' } })
      authUtils.deleteSession.mockRejectedValue(new Error('boom'))

      await expect(logoutHandler(event)).rejects.toMatchObject({ statusCode: 500 })
    })
  })

  describe('POST /api/auth/register', () => {
    it('prüft Pflichtfelder', async () => {
      const event = createEvent()
      mockSuccessReadBody({})

      await expect(registerHandler(event)).rejects.toMatchObject({ statusCode: 400 })
    })

    it('verhindert doppelte Benutzer', async () => {
      const event = createEvent()
      mockSuccessReadBody({ name: 'Max', email: 'max@example.com', password: '12345678', geburtsdatum: '2000-01-01' })
      authUtils.readUsers.mockResolvedValue([{ email: 'max@example.com' }])

      await expect(registerHandler(event)).rejects.toMatchObject({ statusCode: 409 })
    })

    it('verlangt Geburtsdatum bei Registrierung', async () => {
      const event = createEvent()
      mockSuccessReadBody({ name: 'Max', email: 'max@example.com', password: '12345678' })

      await expect(registerHandler(event)).rejects.toMatchObject({ statusCode: 400 })
    })

    it('legt Benutzer an und versendet E-Mails', async () => {
      const event = createEvent()
      mockSuccessReadBody({
        name: 'Max',
        email: 'max@example.com',
        password: '12345678',
        phone: '123',
        geburtsdatum: '2000-01-01',
        visibility: { showBirthday: false }
      })
      authUtils.readUsers.mockResolvedValue([])
      authUtils.hashPassword.mockResolvedValue('hashed')
      authUtils.writeUsers.mockResolvedValue(true)

      const response = await registerHandler(event)

      expect(response.success).toBe(true)
      expect(authUtils.writeUsers).toHaveBeenCalled()
      expect(authUtils.writeUsers.mock.calls[0][0][0]).toMatchObject({
        geburtsdatum: '2000-01-01',
        visibility: { showBirthday: false }
      })
      expect(nodemailer.default.createTransport).toHaveBeenCalled()
    })

    it('benachrichtigt in Testumgebung nicht die Vorstand-Empfänger', async () => {
      process.env.NODE_ENV = 'production'
      process.env.APP_ENV = 'test'

      const event = createEvent()
      mockSuccessReadBody({
        name: 'Max',
        email: 'max@example.com',
        password: '12345678',
        phone: '123',
        geburtsdatum: '2000-01-01'
      })
      authUtils.readUsers.mockResolvedValue([])
      authUtils.hashPassword.mockResolvedValue('hashed')
      authUtils.writeUsers.mockResolvedValue(true)

      await registerHandler(event)

      const transporter = nodemailer.default.createTransport.mock.results[0].value
      expect(transporter.sendMail).toHaveBeenNthCalledWith(1, expect.objectContaining({
        to: 'tsschulz@tsschulz.de'
      }))
    })
  })

  describe('POST /api/auth/reset-password', () => {
    it('prüft Pflichtfelder', async () => {
      const event = createEvent()
      mockSuccessReadBody({})

      const response = await resetPasswordHandler(event)
      expect(response.success).toBe(true)
    })

    it('aktualisiert Passwort bei vorhandenem Benutzer', async () => {
      const event = createEvent()
      const user = { id: '1', email: 'user@example.com', name: 'User', password: 'hash' }
      mockSuccessReadBody({ email: user.email })
      authUtils.readUsers.mockResolvedValue([user])
      authUtils.hashPassword.mockResolvedValue('new-hash')
      authUtils.writeUsers.mockResolvedValue(true)

      const response = await resetPasswordHandler(event)
      expect(response.success).toBe(true)
      expect(authUtils.writeUsers).toHaveBeenCalled()
    })
  })

  describe('GET /api/auth/status', () => {
    it('liefert loggedOut, wenn kein Cookie gesetzt ist', async () => {
      const event = createEvent()
      const response = await statusHandler(event)
      expect(response.isLoggedIn).toBe(false)
    })

    it('liefert Benutzerinformationen bei gültigem Token', async () => {
      const event = createEvent({ cookies: { auth_token: 'token' } })
      authUtils.getUserFromToken.mockResolvedValue({ id: '1', email: 'user@example.com', name: 'User', role: 'mitglied' })

      const response = await statusHandler(event)
      expect(response.isLoggedIn).toBe(true)
      expect(response.user).toMatchObject({ id: '1' })
    })
  })

  describe('GET /api/app/version', () => {
    it('verlangt Login', async () => {
      const event = createEvent()

      await expect(versionHandler(event)).rejects.toMatchObject({ statusCode: 401 })
    })

    it('liefert eingeloggten Benutzern die package.json-Version', async () => {
      const event = createEvent({ cookies: { auth_token: 'token' } })
      authUtils.getUserFromToken.mockResolvedValue({ id: '1', email: 'user@example.com', roles: ['mitglied'] })
      const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))

      const response = await versionHandler(event)

      expect(response.version).toBe(packageJson.version)
    })
  })
})
