import { describe, it, expect, beforeAll, beforeEach, afterAll } from 'vitest'
import {
  createPaymentSession,
  confirmPaymentSession,
  getPaymentSession,
  getCustomerPendingPayments,
} from '../../src/services/payment.service.js'
import { PaymentSessionStatus, SessionStatus } from '../../src/constants.js'
import { resetDb, resetAppConfig, db } from '../helpers/db.js'
import { createCustomer, createMitra } from '../helpers/fixtures.js'

describe('payment.service', () => {
  let customer
  let otherCustomer

  beforeAll(async () => {
    await resetAppConfig()
  })

  beforeEach(async () => {
    await resetDb()
    customer = await createCustomer({ callName: 'Alice' })
    otherCustomer = await createCustomer({ callName: 'Bob' })
  })

  afterAll(async () => {
    // Leave the seeded users alone for the next test file's speed.
  })

  it('createPaymentSession writes a row with status pending and expires_at in the future', async () => {
    const before = Date.now()
    const session = await createPaymentSession({
      customerId: customer.id,
      durationMinutes: 15,
      amount: 30000,
    })

    expect(session.status).toBe(PaymentSessionStatus.PENDING)
    expect(session.customer_id).toBe(customer.id)
    expect(session.duration_minutes).toBe(15)
    expect(session.amount).toBe(30000)
    expect(session.is_first_session_discount).toBe(false)
    expect(session.is_extension).toBe(false)
    expect(session.mode).toBe('chat')
    expect(new Date(session.expires_at).getTime()).toBeGreaterThan(before)

    // Verify it's actually persisted (not just returned from the INSERT)
    const reloaded = await getPaymentSession(session.id)
    expect(reloaded.id).toBe(session.id)
    expect(reloaded.status).toBe(PaymentSessionStatus.PENDING)
  })

  it('confirmPaymentSession transitions pending → confirmed', async () => {
    const session = await createPaymentSession({
      customerId: customer.id,
      durationMinutes: 30,
      amount: 60000,
    })
    expect(session.status).toBe(PaymentSessionStatus.PENDING)

    const confirmed = await confirmPaymentSession(session.id, customer.id)

    expect(confirmed.status).toBe(PaymentSessionStatus.CONFIRMED)
    expect(confirmed.confirmed_at).toBeTruthy()
    expect(new Date(confirmed.confirmed_at).getTime()).toBeGreaterThan(0)
  })

  it('confirmPaymentSession throws when the session belongs to a different customer', async () => {
    const session = await createPaymentSession({
      customerId: customer.id,
      durationMinutes: 15,
      amount: 30000,
    })

    await expect(
      confirmPaymentSession(session.id, otherCustomer.id),
    ).rejects.toMatchObject({
      code: 'FORBIDDEN',
      statusCode: 403,
    })

    // Row should still be pending — the failed confirm must not have side effects.
    const reloaded = await getPaymentSession(session.id)
    expect(reloaded.status).toBe(PaymentSessionStatus.PENDING)
    expect(reloaded.confirmed_at).toBeNull()
  })

  // Phase 4 Stage 10 — Chat Tab Pembayaran feed.
  describe('getCustomerPendingPayments', () => {
    it('returns empty when customer has no payments', async () => {
      const result = await getCustomerPendingPayments(customer.id)
      expect(result.items).toEqual([])
      expect(result.total).toBe(0)
    })

    it('returns pending initial-session payment with null mitra info', async () => {
      const pay = await createPaymentSession({
        customerId: customer.id,
        durationMinutes: 15,
        amount: 5000,
      })

      const result = await getCustomerPendingPayments(customer.id)
      expect(result.total).toBe(1)
      expect(result.items[0]).toMatchObject({
        id: pay.id,
        is_extension: false,
        amount: 5000,
        duration_minutes: 15,
        mode: 'chat',
        mitra_id: null,
        mitra_display_name: null,
      })
    })

    it('fills mitra info from targeted_mitra_id for targeted initial payments', async () => {
      const mitra = await createMitra({ callName: 'kak Dimas' })
      await createPaymentSession({
        customerId: customer.id,
        durationMinutes: 30,
        amount: 10000,
        targetedMitraId: mitra.id,
      })

      const result = await getCustomerPendingPayments(customer.id)
      expect(result.total).toBe(1)
      expect(result.items[0].mitra_id).toBe(mitra.id)
      expect(result.items[0].mitra_display_name).toBe('kak Dimas')
    })

    it('fills mitra info via session_extensions → chat_sessions for extension payments', async () => {
      const sql = db()
      const mitra = await createMitra({ callName: 'kak Sari' })

      // The initial chat session this extension belongs to.
      const [chatSession] = await sql`
        INSERT INTO chat_sessions (customer_id, mitra_id, status, duration_minutes)
        VALUES (${customer.id}, ${mitra.id}, ${SessionStatus.ACTIVE}, 12)
        RETURNING id
      `

      const extPay = await createPaymentSession({
        customerId: customer.id,
        durationMinutes: 10,
        amount: 2500,
        isExtension: true,
      })

      await sql`
        INSERT INTO session_extensions (
          session_id, requested_duration_minutes, requested_price, status, payment_session_id
        )
        VALUES (${chatSession.id}, 10, 2500, 'pending', ${extPay.id})
      `

      const result = await getCustomerPendingPayments(customer.id)
      expect(result.total).toBe(1)
      expect(result.items[0]).toMatchObject({
        id: extPay.id,
        is_extension: true,
        amount: 2500,
        mitra_id: mitra.id,
        mitra_display_name: 'kak Sari',
      })
    })

    it('orders newest first and returns mixed initial + extension rows', async () => {
      const sql = db()
      const mitra = await createMitra({ callName: 'kak Sari' })

      // Initial first
      const initial = await createPaymentSession({
        customerId: customer.id,
        durationMinutes: 15,
        amount: 5000,
      })

      // Then extension (newer)
      const [chatSession] = await sql`
        INSERT INTO chat_sessions (customer_id, mitra_id, status, duration_minutes)
        VALUES (${customer.id}, ${mitra.id}, ${SessionStatus.ACTIVE}, 12)
        RETURNING id
      `
      const extension = await createPaymentSession({
        customerId: customer.id,
        durationMinutes: 10,
        amount: 2500,
        isExtension: true,
      })
      await sql`
        INSERT INTO session_extensions (session_id, requested_duration_minutes, requested_price, status, payment_session_id)
        VALUES (${chatSession.id}, 10, 2500, 'pending', ${extension.id})
      `

      const result = await getCustomerPendingPayments(customer.id)
      expect(result.total).toBe(2)
      // Newest first
      expect(result.items[0].id).toBe(extension.id)
      expect(result.items[1].id).toBe(initial.id)
    })

    it('excludes expired pending rows (defensive filter on expires_at)', async () => {
      const sql = db()
      const pay = await createPaymentSession({
        customerId: customer.id,
        durationMinutes: 15,
        amount: 5000,
      })
      // Manually move expires_at into the past — leaves status pending so this
      // simulates the gap between TTL expiry and the next sweep tick.
      await sql`
        UPDATE payment_sessions
        SET expires_at = NOW() - INTERVAL '1 second'
        WHERE id = ${pay.id}
      `

      const result = await getCustomerPendingPayments(customer.id)
      expect(result.total).toBe(0)
    })

    it('excludes non-pending statuses', async () => {
      const pay = await createPaymentSession({
        customerId: customer.id,
        durationMinutes: 15,
        amount: 5000,
      })
      await confirmPaymentSession(pay.id, customer.id) // → confirmed

      const result = await getCustomerPendingPayments(customer.id)
      expect(result.total).toBe(0)
    })

    it('scopes by customer — does not leak other customers payments', async () => {
      await createPaymentSession({
        customerId: customer.id,
        durationMinutes: 15,
        amount: 5000,
      })
      await createPaymentSession({
        customerId: otherCustomer.id,
        durationMinutes: 30,
        amount: 10000,
      })

      const result = await getCustomerPendingPayments(customer.id)
      expect(result.total).toBe(1)
      expect(result.items[0].amount).toBe(5000)
    })
  })
})
