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

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

vi.mock('../server/utils/news.js', () => ({
  readNews: vi.fn()
}))

const nodemailer = await import('nodemailer')
const newsUtils = await import('../server/utils/news.js')

import contactHandler from '../server/api/contact.post.js'
import galerieHandler from '../server/api/galerie.get.js'
import newsPublicHandler from '../server/api/news-public.get.js'
import termineHandler from '../server/api/termine.get.js'
import spielplaeneHandler from '../server/api/spielplaene.get.js'

describe('Öffentliche API-Endpunkte', () => {
  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.restoreAllMocks()
    vi.clearAllMocks()
  })

  describe('POST /api/contact', () => {
    it('validiert Pflichtfelder', async () => {
      const event = createEvent()
      mockSuccessReadBody({})

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

    it('validiert E-Mail-Adresse', async () => {
      const event = createEvent()
      mockSuccessReadBody({ name: 'Max', email: 'invalid', subject: 'Hi', message: 'Test' })

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

    it('sendet E-Mail bei gültigen Daten', async () => {
      const event = createEvent()
      mockSuccessReadBody({ name: 'Max', email: 'max@example.com', subject: 'Frage', message: 'Hallo' })

      const response = await contactHandler(event)

      expect(response.success).toBe(true)
      expect(nodemailer.default.createTransport).toHaveBeenCalled()
    })

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

      const event = createEvent()
      mockSuccessReadBody({ name: 'Max', email: 'max@example.com', subject: 'Frage', message: 'Hallo' })

      await contactHandler(event)

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

  describe('GET /api/galerie', () => {
    it('liefert leeres Array, wenn Verzeichnis fehlt', async () => {
      vi.spyOn(fs, 'access').mockRejectedValue(new Error('not found'))

      const result = await galerieHandler(createEvent())
      expect(result).toEqual([])
    })

    it('filtert nur Bilddateien', async () => {
      vi.spyOn(fs, 'access').mockResolvedValue(undefined)
      vi.spyOn(fs, 'readdir').mockResolvedValue(['foto-1.jpg', 'readme.md'])

      const result = await galerieHandler(createEvent())
      expect(result).toHaveLength(1)
      expect(result[0].filename).toBe('foto-1.jpg')
    })
  })

  describe('GET /api/news-public', () => {
    it('filtert nicht öffentliche News', async () => {
      const now = new Date()
      newsUtils.readNews.mockResolvedValue([
        { id: 1, isPublic: true, isHidden: false, created: now.toISOString(), title: 'ok' },
        { id: 2, isPublic: false, isHidden: false, created: now.toISOString() },
        { id: 3, isPublic: true, isHidden: true, created: now.toISOString() }
      ])

      const response = await newsPublicHandler(createEvent())
      expect(response.success).toBe(true)
      expect(response.news).toHaveLength(1)
      expect(response.news[0].id).toBe(1)
    })

    it('begrenzt Ergebnis auf drei Einträge', async () => {
      const now = Date.now()
      newsUtils.readNews.mockResolvedValue(
        Array.from({ length: 5 }).map((_, index) => ({
          id: index + 1,
          isPublic: true,
          isHidden: false,
          created: new Date(now - index * 1000).toISOString()
        }))
      )

      const response = await newsPublicHandler(createEvent())
      expect(response.news).toHaveLength(3)
      expect(response.news[0].id).toBe(1)
    })
  })

  describe('GET /api/termine', () => {
    it('parst CSV-Dateien', async () => {
      const csv = 'Datum,Uhrzeit,Titel,Beschreibung,Kategorie\n2025-01-01,19:00,Training,Beschreibung,Sport'
      vi.spyOn(fsPromises, 'readFile').mockResolvedValue(csv)

      const response = await termineHandler(createEvent())
      expect(response.success).toBe(true)
      expect(response.termine[0]).toMatchObject({ titel: 'Training', kategorie: 'Sport' })
    })

    it('gibt leeres Array bei Fehlern zurück', async () => {
      vi.spyOn(fsPromises, 'readFile').mockRejectedValue(new Error('kaputt'))
      const response = await termineHandler(createEvent())
      expect(response.termine).toEqual([])
    })
  })

  describe('GET /api/spielplaene', () => {
    it('filtert nur erlaubte Dateitypen', async () => {
      vi.spyOn(fs, 'access').mockResolvedValue(undefined)
      vi.spyOn(fs, 'readdir').mockResolvedValue(['plan.pdf', 'notizen.txt'])

      const result = await spielplaeneHandler(createEvent())
      expect(result).toEqual(['plan.pdf'])
    })

    it('gibt leeres Array zurück, wenn Verzeichnis fehlt', async () => {
      vi.spyOn(fs, 'access').mockRejectedValue(new Error('not found'))
      const result = await spielplaeneHandler(createEvent())
      expect(result).toEqual([])
    })
  })
})
