import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { closeDb, MAIN_SPACE_ID, runWithSpace } from '@cfdm/db'
import { vpsRepository } from '@cfdm/db/repositories/vps'
import { resetTestDb, seedTestProvider, seedTestProviderAccount } from '@cfdm/db/test-setup'
import { buildApp } from '../index.js'
import { mintIngestToken } from '../services/censorcheck/ingest-token.js'

const SECRET = 'test-censorcheck-ingest-secret'

function ingestPayload(overrides: Record<string, unknown> = {}) {
  return {
    schemaVersion: 1,
    runId: '11111111-1111-4111-8111-111111111111',
    probe: { publicIp: '203.0.113.10' },
    launcherVersion: '1',
    censorcheck: { version: '1', mode: 'both' },
    results: [
      {
        service: 'youtube.com',
        raw: { https: { ipv4: { status: 200 } } },
      },
    ],
    ...overrides,
  }
}

describe('censorcheck ingest + reads', () => {
  let app: Awaited<ReturnType<typeof buildApp>>

  beforeEach(async () => {
    process.env.CENSORCHECK_INGEST_SECRET = SECRET
    process.env.CENSORCHECK_RATE_LIMIT = '0'
    process.env.TRUST_PROXY = '1'
    resetTestDb()
    seedTestProvider('p1')
    seedTestProviderAccount('a1', 'p1')
    app = await buildApp()
  })

  afterEach(async () => {
    await app.close()
    closeDb()
    delete process.env.TRUST_PROXY
  })

  async function post(body: unknown, token?: string, headers: Record<string, string> = {}) {
    return app.inject({
      method: 'POST',
      url: '/api/integrations/censorcheck/runs',
      headers: {
        authorization: `Bearer ${token ?? mintIngestToken(SECRET)}`,
        'content-type': 'application/json',
        ...headers,
      },
      payload: body as object,
    })
  }

  it('отклоняет запрос без токена', async () => {
    const res = await app.inject({
      method: 'POST',
      url: '/api/integrations/censorcheck/runs',
      payload: ingestPayload(),
    })
    expect(res.statusCode).toBe(401)
  })

  it('отклоняет просроченный токен', async () => {
    const token = mintIngestToken(SECRET, 60, Date.now() - 120_000)
    const res = await post(ingestPayload(), token)
    expect(res.statusCode).toBe(401)
  })

  it('отклоняет невалидный payload', async () => {
    const res = await post({ schemaVersion: 1, runId: 'short' })
    expect(res.statusCode).toBe(400)
  })

  it('принимает прогон и оставляет unmatched', async () => {
    const res = await post(ingestPayload())
    expect(res.statusCode).toBe(200)
    const json = res.json() as { matchedVpsId: string | null; probePublicIp: string }
    expect(json.matchedVpsId).toBeNull()
    expect(json.probePublicIp).toBe('203.0.113.10')
  })

  it('матчит VPS по IPv4 и IPv6', async () => {
    const vps = runWithSpace(MAIN_SPACE_ID, () =>
      vpsRepository.create({
        ip: '203.0.113.10',
        ipv6: '2001:db8::55',
        dns: 'edge.example.com',
        providerId: 'p1',
        providerAccountId: 'a1',
        status: 'active',
        tariffType: 'monthly',
        currency: 'RUB',
        vcpu: 2,
        ramGb: 4,
        diskGb: 40,
      }),
    )
    const v4 = await post(ingestPayload({ runId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }))
    expect(v4.json().matchedVpsId).toBe(vps.id)

    const v6 = await post(
      ingestPayload({
        runId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
        probe: { publicIp: '2001:db8::55' },
      }),
    )
    expect(v6.json().matchedVpsId).toBe(vps.id)
  })

  it('повторяет duplicate runId без второй записи', async () => {
    const first = await post(ingestPayload())
    const second = await post(ingestPayload())
    expect(first.statusCode).toBe(200)
    expect(second.statusCode).toBe(200)
    expect(second.json().id).toBe(first.json().id)
    expect(second.json().replayed).toBe(true)

    const current = await app.inject({ method: 'GET', url: '/api/censorcheck/current' })
    expect(current.json().items).toHaveLength(1)
  })

  it('берёт observed XFF, а claimed сохраняет если расходится', async () => {
    runWithSpace(MAIN_SPACE_ID, () =>
      vpsRepository.create({
        ip: '198.51.100.20',
        providerId: 'p1',
        providerAccountId: 'a1',
        status: 'active',
        tariffType: 'monthly',
        currency: 'RUB',
        vcpu: 1,
        ramGb: 1,
        diskGb: 10,
      }),
    )
    const res = await post(ingestPayload({
      runId: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
      probe: { publicIp: '203.0.113.10' },
    }), undefined, { 'x-forwarded-for': '198.51.100.20' })
    const json = res.json() as { probePublicIp: string; matchedVpsId: string | null }
    expect(json.probePublicIp).toBe('198.51.100.20')
    expect(json.matchedVpsId).not.toBeNull()

    const current = await app.inject({ method: 'GET', url: '/api/censorcheck/current' })
    expect(current.json().items[0].claimedPublicIp).toBe('203.0.113.10')
  })

  it('отдаёт историю и детали', async () => {
    await post(ingestPayload())
    const list = await app.inject({ method: 'GET', url: '/api/censorcheck/runs?limit=10' })
    expect(list.statusCode).toBe(200)
    const items = list.json().items as Array<{ id: string; results?: unknown[] }>
    expect(items).toHaveLength(1)
    expect(items[0]!.results).toHaveLength(1)
    const detail = await app.inject({ method: 'GET', url: `/api/censorcheck/runs/${items[0]!.id}` })
    expect(detail.statusCode).toBe(200)
    expect(detail.json().results).toHaveLength(1)
  })

  it('сохраняет хостер из probe и канонизирует ASN', async () => {
    const res = await post(
      ingestPayload({
        runId: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd',
        probe: { publicIp: '203.0.113.10', hoster: 'AS14061 DigitalOcean, LLC' },
      }),
    )
    expect(res.statusCode).toBe(200)
    const current = await app.inject({ method: 'GET', url: '/api/censorcheck/current' })
    expect(current.json().items[0].detectedHoster).toBe('DigitalOcean')
    expect(current.json().items[0].vps).toBeNull()
  })

  it('для matched VPS отдаёт имя хостера из инвентаря', async () => {
    runWithSpace(MAIN_SPACE_ID, () =>
      vpsRepository.create({
        ip: '203.0.113.10',
        dns: 'edge.example.com',
        providerId: 'p1',
        providerAccountId: 'a1',
        status: 'active',
        tariffType: 'monthly',
        currency: 'RUB',
        vcpu: 2,
        ramGb: 4,
        diskGb: 40,
      }),
    )
    await post(
      ingestPayload({
        runId: 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee',
        probe: { publicIp: '203.0.113.10', hoster: 'Hetzner Online GmbH' },
      }),
    )
    const current = await app.inject({ method: 'GET', url: '/api/censorcheck/current' })
    const item = current.json().items[0]
    expect(item.vps.providerName).toBe('Test Host')
    expect(item.detectedHoster).toBe('Hetzner')
  })

  it('принимает results как объект с числовыми ключами (RouterOS serialize)', async () => {
    const res = await post(
      ingestPayload({
        schemaVersion: '1',
        runId: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
        results: {
          '0': {
            service: 'youtube.com',
            raw: { https: { ipv4: { status: 200 } } },
          },
        },
      }),
    )
    expect(res.statusCode).toBe(200)
    expect(res.json().summary.available).toBe(1)
  })
})
