import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import type { Payload } from 'payload'
import type { Tenant } from '@/payload-types'

const mockSendMail = vi.fn(async () => ({ messageId: 'mocked-id' }))
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const mockCreateTransport = vi.fn((_options?: unknown) => ({ sendMail: mockSendMail }))

vi.mock('nodemailer', () => ({
  __esModule: true,
  default: {
    createTransport: (options: unknown) => mockCreateTransport(options),
  },
}))

import {
  sendTenantEmail,
  invalidateTenantEmailCache,
  invalidateGlobalEmailCache,
} from '@/lib/email/tenant-email-service'

describe('tenant email service', () => {
  let payload: Payload
  let mockFindByID: ReturnType<typeof vi.fn>
  let mockCreate: ReturnType<typeof vi.fn>

  beforeEach(() => {
    mockSendMail.mockClear()
    mockCreateTransport.mockClear()
    mockFindByID = vi.fn()
    mockCreate = vi.fn().mockResolvedValue({ id: 1 })

    payload = {
      findByID: mockFindByID,
      create: mockCreate,
      update: vi.fn().mockResolvedValue({}),
    } as unknown as Payload

    process.env.SMTP_HOST = 'smtp.global.test'
    process.env.SMTP_PORT = '587'
    process.env.SMTP_SECURE = 'false'
    process.env.SMTP_USER = 'global-user'
    process.env.SMTP_PASS = 'global-pass'
    process.env.SMTP_FROM_ADDRESS = 'noreply@example.com'

    invalidateGlobalEmailCache()
  })

  afterEach(() => {
    // Restore NODE_ENV after each test
    delete process.env.EMAIL_DELIVERY_DISABLED
  })

  describe('with EMAIL_DELIVERY_DISABLED=false (production mode)', () => {
    beforeEach(() => {
      // Override test environment to simulate production
      vi.stubEnv('NODE_ENV', 'production')
      vi.stubEnv('EMAIL_DELIVERY_DISABLED', 'false')
    })

    afterEach(() => {
      vi.unstubAllEnvs()
    })

    it('falls back to global SMTP configuration when tenant has no custom SMTP', async () => {
      const tenant = {
        id: 1,
        slug: 'tenant-a',
        name: 'Tenant A',
        email: {
          useCustomSmtp: false,
        },
      } as Tenant

      mockFindByID.mockResolvedValue(tenant)

      const result = await sendTenantEmail(payload, tenant.id, {
        to: 'user@example.com',
        subject: 'Test',
        text: 'Hello from test',
      })

      expect(result.success).toBe(true)
      expect(mockCreateTransport).toHaveBeenCalledTimes(1)
      expect(mockCreateTransport).toHaveBeenCalledWith({
        host: 'smtp.global.test',
        port: 587,
        secure: false,
        auth: {
          user: 'global-user',
          pass: 'global-pass',
        },
      })
      expect(mockSendMail).toHaveBeenCalledWith(
        expect.objectContaining({
          from: '"Tenant A" <noreply@example.com>',
          to: 'user@example.com',
        }),
      )
    })

    it('uses cached tenant-specific transporter and re-creates it after invalidation', async () => {
      const tenant = {
        id: 42,
        slug: 'tenant-b',
        name: 'Tenant B',
        email: {
          useCustomSmtp: true,
          fromAddress: 'info@tenant-b.de',
          fromName: 'Tenant B',
          smtp: {
            host: 'smtp.tenant-b.de',
            port: 465,
            secure: true,
            user: 'tenant-user',
            pass: 'tenant-pass',
          },
        },
      } as Tenant

      mockFindByID.mockResolvedValue(tenant)

      await sendTenantEmail(payload, tenant.id, {
        to: 'recipient@example.com',
        subject: 'Hi',
        text: 'First email',
      })

      expect(mockCreateTransport).toHaveBeenCalledTimes(1)
      expect(mockCreateTransport).toHaveBeenCalledWith({
        host: 'smtp.tenant-b.de',
        port: 465,
        secure: true,
        auth: {
          user: 'tenant-user',
          pass: 'tenant-pass',
        },
      })

      mockCreateTransport.mockClear()

      await sendTenantEmail(payload, tenant.id, {
        to: 'recipient@example.com',
        subject: 'Hi again',
        text: 'Second email',
      })

      expect(mockCreateTransport).not.toHaveBeenCalled()

      invalidateTenantEmailCache(tenant.id)

      await sendTenantEmail(payload, tenant.id, {
        to: 'recipient@example.com',
        subject: 'After invalidation',
        text: 'Third email',
      })

      expect(mockCreateTransport).toHaveBeenCalledTimes(1)
    })
  })

  describe('with EMAIL_DELIVERY_DISABLED=true (test mode)', () => {
    it('skips SMTP delivery and returns synthetic message ID', async () => {
      // NODE_ENV=test is default in vitest, which disables email delivery
      const tenant = {
        id: 1,
        slug: 'tenant-a',
        name: 'Tenant A',
        email: {
          useCustomSmtp: false,
        },
      } as Tenant

      mockFindByID.mockResolvedValue(tenant)

      const result = await sendTenantEmail(payload, tenant.id, {
        to: 'user@example.com',
        subject: 'Test',
        text: 'Hello from test',
      })

      expect(result.success).toBe(true)
      expect(result.messageId).toMatch(/^test-message-\d+$/)
      // SMTP should NOT be called in test mode
      expect(mockCreateTransport).not.toHaveBeenCalled()
      expect(mockSendMail).not.toHaveBeenCalled()
    })

    it('creates email log even when delivery is disabled', async () => {
      const tenant = {
        id: 1,
        slug: 'tenant-a',
        name: 'Tenant A',
        email: {
          useCustomSmtp: false,
        },
      } as Tenant

      mockFindByID.mockResolvedValue(tenant)

      await sendTenantEmail(payload, tenant.id, {
        to: 'user@example.com',
        subject: 'Test',
        text: 'Hello from test',
      })

      // Email log should still be created
      expect(mockCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          collection: 'email-logs',
          data: expect.objectContaining({
            to: 'user@example.com',
            subject: 'Test',
            status: 'pending',
          }),
        }),
      )
    })
  })

  describe('cache invalidation', () => {
    it('invalidates tenant-specific cache', () => {
      // This is a simple function that clears cache - just verify it doesn't throw
      expect(() => invalidateTenantEmailCache(42)).not.toThrow()
    })

    it('invalidates global cache', () => {
      expect(() => invalidateGlobalEmailCache()).not.toThrow()
    })
  })
})
