import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Hono } from 'hono'
import { TenantTier } from '@zync/types'
import type { AppEnv } from '../src/types'

const TENANT_ID = '00000000-0000-4000-8000-000000000001'
const USER_ID = '00000000-0000-4000-8000-000000000099'

const mockGetSmtpSettings = vi.fn()
const mockUpdateSmtpSettings = vi.fn()
const mockFindUserById = vi.fn()
const mockEncryptCredential = vi.fn()
const mockValidateSafeOutboundUrl = vi.fn()
const mockTestSmtpConnection = vi.fn()

let mockSession: Record<string, unknown>

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: async (
    c: { set: (key: string, value: unknown) => void },
    next: () => Promise<void>,
  ) => {
    c.set('db', {})
    c.set('session', mockSession)
    await next()
  },
}))

vi.mock('../src/middleware/guards', () => ({
  requirePermission: (permission: string) => {
    return async (
      c: { get: (key: string) => unknown; json: (body: unknown, status: number) => Response },
      next: () => Promise<void>,
    ) => {
      const session = c.get('session') as { permissions?: string[] } | undefined
      if (!session?.permissions?.includes(permission)) {
        return c.json({ error: 'Forbidden' }, 403)
      }
      await next()
    }
  },
}))

vi.mock('@zync/db/queries', () => ({
  createDb: vi.fn(() => ({})),
  getSmtpSettings: (...args: unknown[]) => mockGetSmtpSettings(...args),
  updateSmtpSettings: (...args: unknown[]) => mockUpdateSmtpSettings(...args),
  findUserById: (...args: unknown[]) => mockFindUserById(...args),
}))

vi.mock('@zync/auth', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@zync/auth')>()
  return {
    ...actual,
    encryptCredential: (...args: unknown[]) => mockEncryptCredential(...args),
  }
})

vi.mock('@zync/utils', () => ({
  validateSafeOutboundUrl: (...args: unknown[]) => mockValidateSafeOutboundUrl(...args),
}))

vi.mock('../src/services/onboarding-email-test', () => ({
  testSmtpConnection: (...args: unknown[]) => mockTestSmtpConnection(...args),
}))

import { smtpSettingsRoute } from '../src/routes/settings/smtp'

function appWithRoute() {
  const app = new Hono<AppEnv>()
  app.route('/api/settings/email', smtpSettingsRoute)
  return app
}

function defaultSettings() {
  return {
    host: 'smtp.example.com',
    port: 587,
    username: 'apikey',
    encryptedPassword: '{"ciphertext":"abc"}',
    fromName: 'Acme Ltd',
    fromEmail: 'billing@acme.test',
    domainVerified: false,
    domainVerificationToken: null,
    domainVerifiedAt: null,
    replyTo: null,
    tls: true,
    smtpEncryption: 'starttls' as const,
    smtpFromOverride: null,
    smtpEnabled: false,
    smtpFallbackEnabled: true,
    customDomain: null,
    brandColor: null,
    logoUrl: null,
  }
}

describe('smtp settings route', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockSession = {
      type: 'user',
      sub: USER_ID,
      tid: TENANT_ID,
      role: 'OWNER',
      tier: TenantTier.ENTERPRISE,
      permissions: ['settings:write', 'settings:read'],
    }
    mockGetSmtpSettings.mockResolvedValue(defaultSettings())
    mockUpdateSmtpSettings.mockResolvedValue(undefined)
    mockFindUserById.mockResolvedValue({ id: USER_ID, email: 'owner@acme.test' })
    mockEncryptCredential.mockResolvedValue({ ciphertext: 'enc', iv: 'iv', authTag: 'tag' })
    mockValidateSafeOutboundUrl.mockReturnValue({ ok: true })
    mockTestSmtpConnection.mockResolvedValue({ ok: true })
    vi.stubGlobal('fetch', vi.fn())
  })

  it('returns the spec response shape while preserving legacy fields', async () => {
    const app = appWithRoute()
    const res = await app.request('/api/settings/email')

    expect(res.status).toBe(200)
    const body = await res.json()
    expect(body).toMatchObject({
      from_name: 'Acme Ltd',
      from_email: 'billing@acme.test',
      domain_verified: false,
      smtp_host: 'smtp.example.com',
      smtp_port: 587,
      smtp_username: 'apikey',
      smtp_encryption: 'starttls',
      smtp_enabled: false,
      smtp_fallback_enabled: true,
      smtp_password_set: true,
      host: 'smtp.example.com',
      hasPassword: true,
    })
  })

  it('rejects free-email from addresses', async () => {
    const app = appWithRoute()
    const res = await app.request('/api/settings/email', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ from_email: 'owner@gmail.com' }),
    })

    expect(res.status).toBe(400)
    expect(mockUpdateSmtpSettings).not.toHaveBeenCalled()
  })

  it('requires enterprise tier for relay settings', async () => {
    mockSession.tier = TenantTier.BUSINESS
    const app = appWithRoute()
    const res = await app.request('/api/settings/email', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ smtp_enabled: true }),
    })

    expect(res.status).toBe(402)
    expect(mockUpdateSmtpSettings).not.toHaveBeenCalled()
  })

  it('encrypts smtp_password and persists normalized fields', async () => {
    const app = appWithRoute()
    const res = await app.request('/api/settings/email', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        from_name: 'Acme Billing',
        from_email: 'invoices@acme.test',
        smtp_host: 'smtp.sendgrid.net',
        smtp_port: 465,
        smtp_username: 'apikey',
        smtp_password: 'secret',
        smtp_enabled: true,
        smtp_encryption: 'tls',
      }),
    })

    expect(res.status).toBe(200)
    expect(mockEncryptCredential).toHaveBeenCalledWith('secret', undefined)
    expect(mockUpdateSmtpSettings).toHaveBeenCalledWith(
      {},
      TENANT_ID,
      USER_ID,
      expect.objectContaining({
        fromName: 'Acme Billing',
        fromEmail: 'invoices@acme.test',
        host: 'smtp.sendgrid.net',
        port: 465,
        username: 'apikey',
        smtpEnabled: true,
        smtpEncryption: 'tls',
        encryptedPassword: JSON.stringify({ ciphertext: 'enc', iv: 'iv', authTag: 'tag' }),
      }),
      expect.any(Object),
    )
  })

  it('creates and verifies resend domains', async () => {
    const fetchMock = vi.mocked(fetch)
    fetchMock
      .mockResolvedValueOnce(new Response(JSON.stringify({
        id: 'dom_123',
        records: [{ record: 'DKIM', name: 'resend._domainkey.acme.test', value: 'dkim-value' }],
      }), { status: 200 }))
      .mockResolvedValueOnce(new Response('{}', { status: 200 }))
      .mockResolvedValueOnce(new Response(JSON.stringify({
        id: 'dom_123',
        name: 'acme.test',
        status: 'verified',
        records: [{ record: 'DKIM', name: 'resend._domainkey.acme.test', value: 'dkim-value' }],
      }), { status: 200 }))

    const app = appWithRoute()
    const res = await app.request('/api/settings/email/verify-domain', { method: 'POST' }, {
      RESEND_API_KEY: 'resend-key',
    } as AppEnv['Bindings'])

    expect(res.status).toBe(200)
    const body = await res.json()
    expect(body).toEqual({
      verified: true,
      records: [
        {
          type: 'TXT',
          host: 'resend._domainkey.acme.test',
          value: 'dkim-value',
          status: 'verified',
        },
        {
          type: 'TXT',
          host: 'acme.test',
          value: 'v=spf1 include:spf.resend.com ~all',
          status: 'verified',
        },
      ],
    })
    expect(mockUpdateSmtpSettings).toHaveBeenCalledWith(
      {},
      TENANT_ID,
      USER_ID,
      expect.objectContaining({
        domainVerificationToken: 'dom_123',
        domainVerified: true,
      }),
      expect.any(Object),
    )
  })

  it('tests smtp against the stored configuration and current user email', async () => {
    const app = appWithRoute()
    const res = await app.request('/api/settings/email/test', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({}),
    })

    expect(res.status).toBe(200)
    expect(await res.json()).toEqual({ success: true })
    expect(mockFindUserById).toHaveBeenCalledWith({}, USER_ID)
    expect(mockTestSmtpConnection).toHaveBeenCalledWith({
      host: 'smtp.example.com',
      port: 587,
      username: 'apikey',
      password: '{"ciphertext":"abc"}',
    })
  })
})
