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

const mockListWebhookDeliveries = vi.fn()
const mockGetWebhookDelivery = vi.fn()
const mockGetWebhookEndpointWithSecret = vi.fn()
const mockInsertWebhookDelivery = vi.fn()
const mockDecryptCredential = vi.fn()

vi.mock('@zync/db/queries', () => ({
  createDb: vi.fn(() => ({ kind: 'db' })),
  listWebhookEndpoints: vi.fn(),
  getWebhookEndpoint: vi.fn(),
  getWebhookEndpointWithSecret: (...args: unknown[]) => mockGetWebhookEndpointWithSecret(...args),
  createWebhookEndpoint: vi.fn(),
  updateWebhookEndpoint: vi.fn(),
  updateWebhookSecret: vi.fn(),
  deleteWebhookEndpoint: vi.fn(),
  listWebhookDeliveries: (...args: unknown[]) => mockListWebhookDeliveries(...args),
  getWebhookDelivery: (...args: unknown[]) => mockGetWebhookDelivery(...args),
  insertWebhookDelivery: (...args: unknown[]) => mockInsertWebhookDelivery(...args),
  createWebhookSchema: { safeParse: vi.fn() },
  updateWebhookSchema: { safeParse: vi.fn() },
}))

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

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: async (
    c: { set: (key: string, value: unknown) => void },
    next: () => Promise<void>,
  ) => {
    c.set('session', {
      type: 'user',
      tid: 'tenant-1',
      sub: 'user-1',
      role: 'owner',
      permissions: ['settings:read', 'settings:write'],
    })
    await next()
  },
}))

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

import { webhookEndpointRoutes } from '../src/routes/webhooks/endpoints'

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

function bindings(): AppEnv['Bindings'] {
  return {
    DB: { connectionString: 'postgresql://test/test' },
    INTEGRATION_ENCRYPTION_KEY: 'test-key',
  } as unknown as AppEnv['Bindings']
}

describe('webhook endpoint detail routes', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockDecryptCredential.mockResolvedValue('plaintext-secret')
    mockGetWebhookEndpointWithSecret.mockResolvedValue({
      id: 'endpoint-1',
      tenantId: 'tenant-1',
      url: 'https://hooks.example.com/zync',
      events: [],
      isActive: true,
      secretEncrypted: JSON.stringify({ ciphertext: 'ct', iv: 'iv', authTag: 'tag' }),
    })
    mockInsertWebhookDelivery.mockResolvedValue({
      id: 'delivery-new',
      endpoint_id: 'endpoint-1',
      event_type: 'invoice.paid',
      status: 'failed',
      attempt: 2,
    })
  })

  it('passes status/from/to/page filters through GET /api/webhooks/:id/deliveries', async () => {
    mockListWebhookDeliveries.mockResolvedValue([])

    const res = await app().request(
      '/api/webhooks/endpoint-1/deliveries?status=failed&from=2026-05-01T00:00:00.000Z&to=2026-05-31T23:59:59.000Z&page=3',
      undefined,
      bindings(),
    )

    expect(res.status).toBe(200)
    expect(mockListWebhookDeliveries).toHaveBeenCalledWith(
      expect.anything(),
      'tenant-1',
      'endpoint-1',
      expect.objectContaining({
        status: 'failed',
        from: '2026-05-01T00:00:00.000Z',
        to: '2026-05-31T23:59:59.000Z',
        page: 3,
      }),
    )
  })

  it('returns response_body from POST /api/webhooks/:id/test', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue(
        new Response('{"status":"ok"}', {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        }),
      ),
    )

    const res = await app().request(
      '/api/webhooks/endpoint-1/test',
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ event_type: 'invoice.paid' }),
      },
      bindings(),
    )

    expect(res.status).toBe(200)
    await expect(res.json()).resolves.toMatchObject({
      status: 'test',
      response_status: 200,
      response_body: '{"status":"ok"}',
      latency_ms: expect.any(Number),
    })
  })

  it('rejects retry for non-failed deliveries', async () => {
    mockGetWebhookDelivery.mockResolvedValue({
      id: 'delivery-1',
      endpoint_id: 'endpoint-1',
      event_type: 'invoice.paid',
      request_body: '{"event":"invoice.paid"}',
      attempt: 1,
      status: 'delivered',
    })

    const res = await app().request(
      '/api/webhooks/endpoint-1/deliveries/delivery-1/retry',
      { method: 'POST' },
      bindings(),
    )

    expect(res.status).toBe(409)
    expect(mockInsertWebhookDelivery).not.toHaveBeenCalled()
  })
})
