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

const mockCreateDb = vi.fn(() => ({}))
const mockGetLeadFormBySlug = vi.fn()
const mockCreateFormSubmissionAndLead = vi.fn()
const mockGetLeadWebhookById = vi.fn()
const mockCreateWebhookLead = vi.fn()
const mockTouchWebhookLastReceived = vi.fn()
const mockAddLeadActivity = vi.fn()
const mockListLeadFormSubmissions = vi.fn()
const mockGetLeadForm = vi.fn()
const mockCreateLeadWebhook = vi.fn()

vi.mock('../src/middleware/guards', () => ({
  requirePermission: () => async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('@zync/db/queries', () => ({
  createDb: (...args: unknown[]) => mockCreateDb(...args),
  getLeadFormBySlug: (...args: unknown[]) => mockGetLeadFormBySlug(...args),
  createFormSubmissionAndLead: (...args: unknown[]) => mockCreateFormSubmissionAndLead(...args),
  getLeadWebhookById: (...args: unknown[]) => mockGetLeadWebhookById(...args),
  createWebhookLead: (...args: unknown[]) => mockCreateWebhookLead(...args),
  touchWebhookLastReceived: (...args: unknown[]) => mockTouchWebhookLastReceived(...args),
  addLeadActivity: (...args: unknown[]) => mockAddLeadActivity(...args),
  publicFormSubmitSchema: {
    safeParse: vi.fn((body: unknown) => ({ success: true, data: body })),
  },
  listLeadWebhooks: vi.fn(),
  getLeadWebhook: vi.fn(),
  createLeadWebhook: (...args: unknown[]) => mockCreateLeadWebhook(...args),
  updateLeadWebhook: vi.fn(),
  deleteLeadWebhook: vi.fn(),
  createLeadWebhookSchema: {
    safeParse: vi.fn((body: unknown) => ({ success: true, data: body })),
  },
  updateLeadWebhookSchema: {
    safeParse: vi.fn((body: unknown) => ({ success: true, data: body })),
  },
  listLeadForms: vi.fn(),
  getLeadForm: (...args: unknown[]) => mockGetLeadForm(...args),
  createLeadForm: vi.fn(),
  updateLeadForm: vi.fn(),
  deleteLeadForm: vi.fn(),
  listLeadFormSubmissions: (...args: unknown[]) => mockListLeadFormSubmissions(...args),
  createLeadFormSchema: { safeParse: vi.fn() },
  updateLeadFormSchema: { safeParse: vi.fn() },
}))

import { publicFormRoutes } from '../src/routes/marketing/public-form'
import { webhooksRoute } from '../src/routes/marketing/webhooks'
import { formsRoute } from '../src/routes/marketing/forms'

function publicApp() {
  const app = new Hono<AppEnv>()
  app.route('/api', publicFormRoutes)
  return app
}

function authedApp(routeBase: string, route: Hono<AppEnv>) {
  const app = new Hono<AppEnv>()
  app.use('*', async (c, next) => {
    c.set('session', {
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      tier: 'business',
      permissions: ['marketing:read', 'marketing:write'],
    })
    c.set('db', {})
    await next()
  })
  app.route(routeBase, route)
  return app
}

describe('marketing leads public route contract', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockGetLeadFormBySlug.mockResolvedValue({
      id: 'form-1',
      tenantId: 'tenant-1',
      name: 'Contact us',
      slug: 'contact-us',
      fields: [],
      redirectUrl: 'https://example.com/thanks',
      style: { primaryColor: '#000000' },
      isActive: true,
    })
    mockCreateFormSubmissionAndLead.mockResolvedValue({
      lead: {
        id: 'lead-1',
        tenantId: 'tenant-1',
        utmSource: 'newsletter',
        utmMedium: 'email',
        utmCampaign: 'summer',
      },
      submissionId: 'submission-1',
    })
    mockGetLeadWebhookById.mockResolvedValue({
      id: 'webhook-1',
      tenantId: 'tenant-1',
      source: 'generic',
      secret: '',
      isActive: true,
    })
    mockCreateWebhookLead.mockResolvedValue({
      id: 'lead-2',
      tenantId: 'tenant-1',
      utmSource: null,
      utmMedium: null,
      utmCampaign: null,
    })
    mockTouchWebhookLastReceived.mockResolvedValue(undefined)
    mockAddLeadActivity.mockResolvedValue(undefined)
  })

  it('serves public form config at GET /api/forms/:slug/public', async () => {
    const res = await publicApp().request('/api/forms/contact-us/public?tenant=tenant-slug', undefined, {
      DB: { connectionString: 'postgresql://test/test' },
    } as AppEnv['Bindings'])

    expect(res.status).toBe(200)
    expect(mockGetLeadFormBySlug).toHaveBeenCalledWith({}, 'tenant-slug', 'contact-us')
  })

  it('accepts public submission at POST /api/forms/:slug', async () => {
    const res = await publicApp().request('/api/forms/contact-us?tenant=tenant-1&utm_source=newsletter&utm_medium=email&utm_campaign=summer', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        payload: {
          '11111111-1111-1111-1111-111111111111': 'Ada Lovelace',
        },
      }),
    }, {
      DB: { connectionString: 'postgresql://test/test' },
      RATE_LIMITER_LEAD_FORM: { limit: vi.fn().mockResolvedValue({ success: true }) },
    } as unknown as AppEnv['Bindings'])

    expect(res.status).toBe(200)
    expect(mockCreateFormSubmissionAndLead).toHaveBeenCalledWith(
      {},
      'tenant-1',
      expect.any(Object),
      expect.objectContaining({
        payload: { '11111111-1111-1111-1111-111111111111': 'Ada Lovelace' },
        utm_source: 'newsletter',
        utm_medium: 'email',
        utm_campaign: 'summer',
      }),
      expect.any(Object),
    )
  })

  it('accepts public submission at POST /api/forms/:slug/submit', async () => {
    const res = await publicApp().request('/api/forms/contact-us/submit?tenant=tenant-1&utm_source=newsletter&utm_medium=email&utm_campaign=summer', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        payload: {
          '11111111-1111-1111-1111-111111111111': 'Ada Lovelace',
        },
      }),
    }, {
      DB: { connectionString: 'postgresql://test/test' },
      RATE_LIMITER_LEAD_FORM: { limit: vi.fn().mockResolvedValue({ success: true }) },
    } as unknown as AppEnv['Bindings'])

    expect(res.status).toBe(200)
    expect(mockCreateFormSubmissionAndLead).toHaveBeenCalledWith(
      {},
      'tenant-1',
      expect.any(Object),
      expect.objectContaining({
        payload: { '11111111-1111-1111-1111-111111111111': 'Ada Lovelace' },
      }),
      expect.any(Object),
    )
  })
})

describe('marketing leads webhook config routes', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockCreateLeadWebhook.mockResolvedValue({
      id: 'webhook-1',
      tenantId: 'tenant-1',
      name: 'FB leads',
      source: 'facebook',
      fieldMapping: null,
      isActive: true,
      lastReceivedAt: null,
      createdAt: '2026-07-01T00:00:00.000Z',
    })
  })

  it('returns the plaintext secret once on create', async () => {
    const res = await authedApp('/api/marketing/webhooks', webhooksRoute).request('/api/marketing/webhooks', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        name: 'FB leads',
        source: 'facebook',
        secret: 'super-secret',
      }),
    }, {
      DB: { connectionString: 'postgresql://test/test' },
      INTEGRATION_ENCRYPTION_KEY: '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff',
    } as AppEnv['Bindings'])

    expect(res.status).toBe(201)
    await expect(res.json()).resolves.toMatchObject({
      webhook: expect.objectContaining({ id: 'webhook-1' }),
      secret: 'super-secret',
    })
  })
})

describe('marketing lead forms routes', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockGetLeadForm.mockResolvedValue({
      id: 'form-1',
      tenantId: 'tenant-1',
      name: 'Contact us',
      slug: 'contact-us',
      submissionCount: 2,
    })
    mockListLeadFormSubmissions.mockResolvedValue({
      items: [{ id: 'submission-1' }, { id: 'submission-2' }],
      nextCursor: 'cursor-2',
      total: 2,
    })
  })

  it('returns paginated submissions from GET /api/marketing/forms/:id/submissions', async () => {
    const res = await authedApp('/api/marketing/forms', formsRoute).request('/api/marketing/forms/form-1/submissions?cursor=cursor-1&limit=25', undefined, {} as AppEnv['Bindings'])

    expect(res.status).toBe(200)
    expect(mockListLeadFormSubmissions).toHaveBeenCalledWith({}, 'tenant-1', 'form-1', 25, 'cursor-1')
    await expect(res.json()).resolves.toEqual({
      items: [{ id: 'submission-1' }, { id: 'submission-2' }],
      nextCursor: 'cursor-2',
      total: 2,
    })
  })
})
