import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

vi.mock('../../lib/prisma', () => ({
  prisma: {
    pricingConfig: { findMany: vi.fn(), findUnique: vi.fn() },
    planFeature: { findMany: vi.fn() },
    company: { findUniqueOrThrow: vi.fn() },
    subscription: { update: vi.fn() },
  },
}))
vi.mock('../../services/amanpayService', () => ({
  isConfigured: vi.fn(),
  createCheckout: vi.fn(),
}))
vi.mock('../../services/paypalService', () => ({
  isConfigured: vi.fn(),
  createOrder: vi.fn(),
  captureOrder: vi.fn(),
}))
vi.mock('./subscription.repo', () => ({
  findByCompany: vi.fn(),
  findById: vi.fn(),
  findInvoices: vi.fn(),
  getEvents: vi.fn(),
  startTrial: vi.fn(),
  createEvent: vi.fn(),
  findInvoiceByAmanpay: vi.fn(),
  findInvoiceByPaypal: vi.fn(),
  findInvoiceByPaypalForCompany: vi.fn(),
  findOrCreateSubscription: vi.fn(),
  createInvoice: vi.fn(),
  markInvoicePaid: vi.fn(),
  markInvoiceFailed: vi.fn(),
  createPaymentAttempt: vi.fn(),
  incrementRetryCount: vi.fn(),
  activateSubscription: vi.fn(),
  setPaymentPending: vi.fn(),
  updateInvoicePaypal: vi.fn(),
  updatePlan: vi.fn(),
  setCancelled: vi.fn(),
  setCancelAtPeriodEnd: vi.fn(),
  findTrialsEndedWithoutConversion: vi.fn(),
  findPaymentPendingTimedOut: vi.fn(),
  findPastDueTimedOut: vi.fn(),
  findSuspendedTimedOut: vi.fn(),
  findSubscriptionsToExpireAtPeriodEnd: vi.fn(),
  setExpired: vi.fn(),
  setPastDue: vi.fn(),
  setSuspended: vi.fn(),
}))

import { prisma } from '../../lib/prisma'
import * as amanpay from '../../services/amanpayService'
import * as paypal from '../../services/paypalService'
import * as repo from './subscription.repo'
import * as service from './subscription.service'

describe('subscription.service operational edges', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.useFakeTimers()
    vi.setSystemTime(new Date('2026-06-01T00:00:00.000Z'))
    process.env.API_URL = 'https://api.example.test'
  })

  afterEach(() => {
    vi.useRealTimers()
    delete process.env.API_URL
  })

  it('builds plans from pricing config rows when platform overrides exist', async () => {
    vi.mocked(prisma.pricingConfig.findMany).mockResolvedValue([
      { plan: 'STARTER', billingPeriod: 'MONTHLY', amount: 14900 },
      { plan: 'STARTER', billingPeriod: 'ANNUAL', amount: 143040 },
      { plan: 'PRO', billingPeriod: 'MONTHLY', amount: 39900 },
    ] as never)

    await expect(service.getPlans()).resolves.toEqual({
      STARTER: {
        MONTHLY: { MAD: 14900 },
        ANNUAL: { MAD: 143040 },
      },
      PRO: {
        MONTHLY: { MAD: 39900 },
      },
    })
  })

  it('starts trials only after enforcing one-trial-per-company policy', async () => {
    vi.mocked(repo.findByCompany).mockResolvedValue({ id: 'sub_old', trialUsed: true } as never)

    await expect(service.startTrial('company_1', 'PRO', 'MONTHLY', 'MAD')).rejects.toThrow('already used its free trial')
    expect(repo.startTrial).not.toHaveBeenCalled()

    vi.mocked(repo.findByCompany).mockResolvedValue(null as never)
    vi.mocked(repo.startTrial).mockResolvedValue({ id: 'sub_1' } as never)

    await service.startTrial('company_1', 'PRO', 'MONTHLY', 'MAD')

    expect(repo.startTrial).toHaveBeenCalledWith(
      'company_1',
      'PRO',
      'MONTHLY',
      'MAD',
      new Date('2026-07-01T00:00:00.000Z'),
    )
    expect(repo.createEvent).toHaveBeenCalledWith(expect.objectContaining({
      subscriptionId: 'sub_1',
      companyId: 'company_1',
      eventType: 'trial.started',
    }))
  })

  it('creates AmanPay checkout invoices with webhook metadata and due dates', async () => {
    vi.mocked(prisma.pricingConfig.findUnique).mockResolvedValue({ amount: 19900 } as never)
    vi.mocked(prisma.company.findUniqueOrThrow).mockResolvedValue({ email: 'owner@example.test', name: 'Atlas Cars' } as never)
    vi.mocked(repo.findOrCreateSubscription).mockResolvedValue({ id: 'sub_1' } as never)
    vi.mocked(amanpay.isConfigured).mockReturnValue(true)
    vi.mocked(amanpay.createCheckout).mockResolvedValue({ checkoutUrl: 'https://pay.example.test/checkout', transactionId: 'txn_1' } as never)
    vi.mocked(repo.createInvoice).mockResolvedValue({ id: 'invoice_1' } as never)

    await expect(service.checkout('company_1', {
      plan: 'GROWTH',
      billingPeriod: 'MONTHLY',
      currency: 'MAD',
      provider: 'AMANPAY',
      successUrl: 'https://app.example.test/success',
      failureUrl: 'https://app.example.test/failure',
    })).resolves.toEqual({ invoice: { id: 'invoice_1' }, checkoutUrl: 'https://pay.example.test/checkout' })

    expect(amanpay.createCheckout).toHaveBeenCalledWith(expect.objectContaining({
      amount: 19900,
      currency: 'MAD',
      customerEmail: 'owner@example.test',
      customerName: 'Atlas Cars',
      webhookUrl: 'https://api.example.test/api/v1/subscriptions/webhooks/amanpay',
    }))
    expect(repo.createInvoice).toHaveBeenCalledWith(expect.objectContaining({
      companyId: 'company_1',
      subscriptionId: 'sub_1',
      amount: 19900,
      paymentProvider: 'AMANPAY',
      amanpayTransactionId: 'txn_1',
      paypalCaptureId: null,
      dueAt: new Date('2026-06-08T00:00:00.000Z'),
    }))
  })

  it('keeps paid PayPal capture idempotent and avoids provider capture', async () => {
    vi.mocked(repo.findInvoiceByPaypalForCompany).mockResolvedValue({ status: 'PAID' } as never)

    await expect(service.capturePaypal('company_1', 'order_1')).resolves.toEqual({ success: true })

    expect(paypal.captureOrder).not.toHaveBeenCalled()
    expect(repo.activateSubscription).not.toHaveBeenCalled()
  })

  it('advances payment-pending subscriptions to past-due in the scheduled job', async () => {
    vi.mocked(repo.findPaymentPendingTimedOut).mockResolvedValue([
      { id: 'sub_1', companyId: 'company_1', paymentPendingSince: new Date('2026-05-20T00:00:00.000Z') },
      { id: 'sub_2', companyId: 'company_2', paymentPendingSince: new Date('2026-05-21T00:00:00.000Z') },
    ] as never)

    await expect(service.runPaymentPendingTimeoutJob()).resolves.toBe(2)

    expect(repo.findPaymentPendingTimedOut).toHaveBeenCalledWith(new Date('2026-05-25T00:00:00.000Z'))
    expect(repo.setPastDue).toHaveBeenCalledTimes(2)
    expect(repo.createEvent).toHaveBeenCalledWith(expect.objectContaining({
      subscriptionId: 'sub_1',
      companyId: 'company_1',
      eventType: 'subscription.past_due',
    }))
  })

  it('rejects user cancellation when no subscription exists', async () => {
    vi.mocked(repo.findByCompany).mockResolvedValue(null as never)

    await expect(service.cancel('company_1')).rejects.toThrow('No subscription found')
    expect(repo.setCancelled).not.toHaveBeenCalled()
  })

  it('records admin suspension events with actor and reason', async () => {
    vi.mocked(repo.findById).mockResolvedValue({ id: 'sub_1', companyId: 'company_1' } as never)

    await expect(service.adminSuspendSubscription('sub_1', 'admin_1', 'fraud review')).resolves.toEqual({ success: true })

    expect(repo.setSuspended).toHaveBeenCalledWith('sub_1')
    expect(repo.createEvent).toHaveBeenCalledWith({
      subscriptionId: 'sub_1',
      companyId: 'company_1',
      eventType: 'subscription.suspended',
      source: 'admin',
      payload: { adminId: 'admin_1', reason: 'fraud review' },
    })
  })
})
