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

const mockCreateNotification = vi.fn()
const mockGetExpiredTrials = vi.fn()
const mockSetGracePeriodStarted = vi.fn()
const mockDowngradeToFreelancerDb = vi.fn()
const mockGetActiveTrials = vi.fn()
const mockStampTrialWarningSent = vi.fn()
const mockSendEmail = vi.fn()
const mockSyncTierToTenant = vi.fn()
const mockDbExecute = vi.fn()

vi.mock('@zync/db', () => ({
  sql: (strings: TemplateStringsArray, ...values: unknown[]) => ({ strings, values }),
}))

vi.mock('@zync/db/queries', () => ({
  createDb: vi.fn(() => ({ execute: mockDbExecute })),
  getExpiredTrials: (...args: unknown[]) => mockGetExpiredTrials(...args),
  downgradeToFreelancerDb: (...args: unknown[]) => mockDowngradeToFreelancerDb(...args),
  setGracePeriodStarted: (...args: unknown[]) => mockSetGracePeriodStarted(...args),
  createNotification: (...args: unknown[]) => mockCreateNotification(...args),
  getActiveTrials: (...args: unknown[]) => mockGetActiveTrials(...args),
  stampTrialWarningSent: (...args: unknown[]) => mockStampTrialWarningSent(...args),
}))

vi.mock('@zync/payments', () => ({
  syncTierToTenant: (...args: unknown[]) => mockSyncTierToTenant(...args),
}))

vi.mock('@zync/notifications', () => ({
  sendEmail: (...args: unknown[]) => mockSendEmail(...args),
}))

vi.mock('@zync/auth', async () => {
  const actual = await vi.importActual<typeof import('@zync/auth')>('@zync/auth')
  return {
    ...actual,
    timingSafeEqual: vi.fn(() => true),
  }
})

import { subscriptionTrialCheckRoute } from '../src/routes/cron/subscription-trial-check'

const mockEnv = {
  CRON_SECRET: '1234567890abcdef',
} as AppEnv['Bindings']

function appWithRoute() {
  const app = new Hono<AppEnv>()
  app.route('/api/cron/subscription-trial-check', subscriptionTrialCheckRoute)
  return app
}

describe('POST /api/cron/subscription-trial-check', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockGetExpiredTrials.mockResolvedValue([])
    mockGetActiveTrials.mockResolvedValue([])
    mockDbExecute.mockResolvedValue([{ user_id: 'owner-1', email: 'owner@example.com' }])
  })

  it('sends a T-3 milestone notification and email once, then stamps the warning timestamp', async () => {
    mockGetActiveTrials.mockResolvedValueOnce([
      {
        tenantId: 'tenant-1',
        trialEndsAt: new Date(Date.now() + 3 * 86_400_000 - 1_000),
        trialWarningSentAt: null,
      },
    ])

    const res = await appWithRoute().request(
      '/api/cron/subscription-trial-check',
      {
        method: 'POST',
        headers: { 'x-cron-secret': '1234567890abcdef' },
      },
      mockEnv,
    )

    expect(res.status).toBe(200)
    expect(mockCreateNotification).toHaveBeenCalledWith(
      expect.anything(),
      expect.objectContaining({
        tenantId: 'tenant-1',
        userId: 'owner-1',
        type: 'trial_expiring',
        params: { days: '3' },
      }),
    )
    expect(mockSendEmail).toHaveBeenCalledWith(
      expect.objectContaining({
        to: 'owner@example.com',
        vars: expect.objectContaining({
          subject: 'Your Zync Business trial ends in 3 days',
        }),
      }),
      mockEnv,
    )
    expect(mockStampTrialWarningSent).toHaveBeenCalledWith(expect.anything(), 'tenant-1')
  })

  it('does not resend the same milestone on the same UTC day', async () => {
    mockGetActiveTrials.mockResolvedValueOnce([
      {
        tenantId: 'tenant-1',
        trialEndsAt: new Date(Date.now() + 86_400_000 - 1_000),
        trialWarningSentAt: new Date(),
      },
    ])

    const res = await appWithRoute().request(
      '/api/cron/subscription-trial-check',
      {
        method: 'POST',
        headers: { 'x-cron-secret': '1234567890abcdef' },
      },
      mockEnv,
    )

    expect(res.status).toBe(200)
    expect(mockCreateNotification).not.toHaveBeenCalled()
    expect(mockSendEmail).not.toHaveBeenCalled()
    expect(mockStampTrialWarningSent).not.toHaveBeenCalled()
  })
})
