// @vitest-environment node
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'
import { sql } from 'drizzle-orm'
import { makeTestDb, closeTestDb, resetTables, type TestDb } from './test/pglite-db.js'
import {
  createUser,
  createReferralLink,
  seedReferralSettings,
} from './test/fixtures.js'
import { createPgliteFraudHostStore } from './test/host-store-fixture.js'
import { referralsTable } from './schema.js'

let testDb: TestDb

const getAttribution = () => import('./attribution.js')

describe('attribution', () => {
  beforeAll(async () => {
    testDb = await makeTestDb()
    // The self-referral check detects a shared phone blind-index across two accounts;
    // harness DDL has UNIQUE(phone_index) — drop for this oracle only.
    await testDb.execute(sql`
      ALTER TABLE users DROP CONSTRAINT IF EXISTS users_phone_index_key
    `)
  })

  afterAll(async () => {
    await closeTestDb()
  })

  beforeEach(async () => {
    await resetTables(testDb)
    await seedReferralSettings(testDb)
    const { resetAdaptersForTest } = await import('./fraud/registry.js')
    resetAdaptersForTest()
  })

  describe('resolveRefCode', () => {
    it('returns active link for matching code', async () => {
      const { resolveRefCode } = await getAttribution()
      const { id: ownerUserId } = await createUser(testDb)
      const { id: linkId, code } = await createReferralLink(testDb, ownerUserId, {
        code: 'ACTIVE-CODE',
      })

      const link = await resolveRefCode(testDb, code)

      expect(link).not.toBeNull()
      expect(link!.id).toBe(linkId)
      expect(link!.code).toBe('ACTIVE-CODE')
      expect(link!.active).toBe(true)
    })

    it('returns null for unknown code', async () => {
      const { resolveRefCode } = await getAttribution()
      expect(await resolveRefCode(testDb, 'NO-SUCH-CODE')).toBeNull()
    })

    it('returns null when link is inactive', async () => {
      const { resolveRefCode } = await getAttribution()
      const { id: ownerUserId } = await createUser(testDb)
      const { code } = await createReferralLink(testDb, ownerUserId, { code: 'INACTIVE' })

      await testDb.execute(sql`
        UPDATE referral_links SET active = false WHERE code = ${'INACTIVE'}
      `)

      expect(await resolveRefCode(testDb, code)).toBeNull()
    })
  })

  describe('isSelfReferral', () => {
    const host = () => createPgliteFraudHostStore(testDb)

    it('returns true for same user id', async () => {
      const { isSelfReferral } = await getAttribution()
      const userId = crypto.randomUUID()

      expect(
        await isSelfReferral(host(), { referrerUserId: userId, refereeUserId: userId }),
      ).toBe(true)
    })

    it('returns false for different users without shared phone index', async () => {
      const { isSelfReferral } = await getAttribution()
      const { id: referrerUserId } = await createUser(testDb)
      const { id: refereeUserId } = await createUser(testDb)

      expect(
        await isSelfReferral(host(), { referrerUserId, refereeUserId }),
      ).toBe(false)
    })

    it('returns true when users share the same phone blind-index', async () => {
      const { isSelfReferral } = await getAttribution()
      const phoneIndex = 'phone-blind-index-shared'
      const referrerUserId = crypto.randomUUID()
      const refereeUserId = crypto.randomUUID()

      await testDb.execute(sql`
        INSERT INTO users (id, email, phone_index) VALUES
          (${referrerUserId}, ${'ref-' + referrerUserId + '@test.invalid'}, ${phoneIndex}),
          (${refereeUserId}, ${'ee-' + refereeUserId + '@test.invalid'}, ${phoneIndex})
      `)

      expect(
        await isSelfReferral(host(), { referrerUserId, refereeUserId }),
      ).toBe(true)
    })
  })

  describe('bindReferralOnSignup', () => {
    const host = () => createPgliteFraudHostStore(testDb)

    it('returns null when linkId not found', async () => {
      const { bindReferralOnSignup } = await getAttribution()
      const { id: refereeUserId } = await createUser(testDb)

      const result = await bindReferralOnSignup(testDb, {
        refereeUserId,
        linkId: crypto.randomUUID(),
        host: host(),
      })

      expect(result).toBeNull()
    })

    it('returns null on self-referral (link owner is referee)', async () => {
      const { bindReferralOnSignup } = await getAttribution()
      const { id: userId } = await createUser(testDb)
      const { id: linkId } = await createReferralLink(testDb, userId)

      const result = await bindReferralOnSignup(testDb, {
        refereeUserId: userId,
        linkId,
        host: host(),
      })

      expect(result).toBeNull()
    })

    it('creates referral row for valid link (last-click bind)', async () => {
      const { bindReferralOnSignup } = await getAttribution()
      const { id: referrerUserId } = await createUser(testDb)
      const { id: refereeUserId } = await createUser(testDb)
      const { id: linkId } = await createReferralLink(testDb, referrerUserId)
      const clickedAt = new Date('2026-01-15T12:00:00.000Z')

      const result = await bindReferralOnSignup(testDb, {
        refereeUserId,
        linkId,
        clickedAt,
        host: host(),
      })

      expect(result).not.toBeNull()
      expect(result!.refereeUserId).toBe(refereeUserId)
      expect(result!.referrerUserId).toBe(referrerUserId)
      expect(result!.linkId).toBe(linkId)
      expect(result!.status).toBe('pending')
      expect(result!.clickedAt?.toISOString()).toBe(clickedAt.toISOString())
    })

    it('duplicate referee returns existing row without creating duplicate', async () => {
      const { bindReferralOnSignup } = await getAttribution()
      const { id: referrerUserId } = await createUser(testDb)
      const { id: refereeUserId } = await createUser(testDb)
      const { id: linkId } = await createReferralLink(testDb, referrerUserId)

      const first = await bindReferralOnSignup(testDb, {
        refereeUserId,
        linkId,
        host: host(),
      })
      expect(first).not.toBeNull()

      const second = await bindReferralOnSignup(testDb, {
        refereeUserId,
        linkId,
        host: host(),
      })

      const rows = await testDb.select().from(referralsTable)
      expect(rows).toHaveLength(1)
      if (second !== null) {
        expect(second.id).toBe(first!.id)
      }
    })
  })
})
