import { PLAN_PRICES } from '@rentaldrivego/types'
import { prisma } from '../../lib/prisma'
import { ValidationError } from '../../http/errors'
import * as amanpay from '../../services/amanpayService'
import * as paypal from '../../services/paypalService'
import * as repo from './subscription.repo'
import { SUBSCRIPTION_POLICY, getAccessLevel } from './subscription.policy'
import { getWebhookEventId, processWebhookOnce } from '../../security/webhookIdempotency'

// ─── Helpers ──────────────────────────────────────────────────

export function addPeriod(date: Date, period: string): Date {
  const d = new Date(date)
  period === 'ANNUAL' ? d.setFullYear(d.getFullYear() + 1) : d.setMonth(d.getMonth() + 1)
  return d
}

// ─── Plans & providers ────────────────────────────────────────

export async function getPlans() {
  const configs = await prisma.pricingConfig.findMany()
  if (configs.length === 0) return PLAN_PRICES
  const result: Record<string, Record<string, Record<string, number>>> = {}
  for (const c of configs) {
    if (!result[c.plan]) result[c.plan] = {}
    result[c.plan]![c.billingPeriod] = { MAD: c.amount }
  }
  return result
}

export function getProviders() {
  return { amanpay: amanpay.isConfigured(), paypal: paypal.isConfigured() }
}

export function getPlanFeatures() {
  return prisma.planFeature.findMany({
    orderBy: [{ plan: 'asc' }, { sortOrder: 'asc' }, { createdAt: 'asc' }],
  })
}

// ─── Reads ────────────────────────────────────────────────────

export function getSubscription(companyId: string) {
  return repo.findByCompany(companyId)
}

export function getInvoices(companyId: string) {
  return repo.findInvoices(companyId)
}

export function getEvents(companyId: string) {
  return repo.findByCompany(companyId).then((sub: any) =>
    sub ? repo.getEvents(sub.id) : [],
  )
}

export function getEventsBySubscriptionId(subscriptionId: string) {
  return repo.getEvents(subscriptionId)
}

// ─── Entitlements ─────────────────────────────────────────────

export async function getEntitlement(companyId: string) {
  const sub = await repo.findByCompany(companyId)
  const status = sub?.status ?? 'EXPIRED'
  const accessLevel = getAccessLevel(status)
  return {
    companyId,
    subscriptionId:     sub?.id ?? null,
    subscriptionStatus: status,
    accessLevel,
    plan:               sub?.plan ?? null,
    currentPeriodEnd:   sub?.currentPeriodEnd ?? null,
  }
}

// ─── Trial management ─────────────────────────────────────────

export async function startTrial(
  companyId: string,
  plan: string,
  billingPeriod: string,
  currency: string,
) {
  if (SUBSCRIPTION_POLICY.trial.oneTrialPerCompany) {
    const existing = await repo.findByCompany(companyId)
    if (existing?.trialUsed) {
      throw new ValidationError('This company has already used its free trial')
    }
  }

  const trialEndAt = new Date(
    Date.now() + SUBSCRIPTION_POLICY.trial.durationDays * 24 * 60 * 60 * 1000,
  )
  const sub = await repo.startTrial(companyId, plan, billingPeriod, currency, trialEndAt)
  await repo.createEvent({
    subscriptionId: sub.id,
    companyId,
    eventType: 'trial.started',
    payload: { plan, billingPeriod, trialEndAt },
  })
  return sub
}

// ─── Payment success (shared by all providers) ───────────────

async function handlePaymentSuccess(subscriptionId: string, invoiceId: string) {
  const sub = await repo.findById(subscriptionId)
  if (!sub) return

  await repo.markInvoicePaid(invoiceId)
  await repo.createPaymentAttempt({
    invoiceId,
    subscriptionId,
    status: 'succeeded',
  })

  const periodEnd = addPeriod(new Date(), sub.billingPeriod)
  await repo.activateSubscription(subscriptionId, periodEnd)
  await repo.createEvent({
    subscriptionId,
    companyId: sub.companyId,
    eventType: sub.status === 'TRIALING' ? 'trial.converted' : 'subscription.activated',
    source: 'webhook',
    payload: { invoiceId, periodEnd },
  })
}

// ─── Payment failure ──────────────────────────────────────────

async function handlePaymentFailure(
  subscriptionId: string,
  invoiceId: string,
  failureCode?: string,
  failureMessage?: string,
) {
  const sub = await repo.findById(subscriptionId)
  if (!sub) return

  await repo.markInvoiceFailed(invoiceId)
  await repo.createPaymentAttempt({
    invoiceId,
    subscriptionId,
    status: 'failed',
    failureCode,
    failureMessage,
  })
  await repo.incrementRetryCount(sub.id)

  if (sub.status !== 'PAYMENT_PENDING') {
    await repo.setPaymentPending(sub.id)
    await repo.createEvent({
      subscriptionId: sub.id,
      companyId: sub.companyId,
      eventType: 'subscription.payment_pending',
      source: 'webhook',
      payload: { invoiceId, failureCode },
    })
  }
}

// ─── Webhook handlers ─────────────────────────────────────────

async function applyAmanpayWebhook(event: any) {
  const transactionId = event.transaction_id ?? event.id
  const status        = event.status?.toUpperCase()

  if (status === 'PAID' || status === 'SUCCEEDED') {
    const invoice = await repo.findInvoiceByAmanpay(transactionId)
    if (!invoice || invoice.status === 'PAID') return  // idempotent
    await handlePaymentSuccess(invoice.subscriptionId, invoice.id)
  } else if (status === 'FAILED' || status === 'DECLINED') {
    const invoice = await repo.findInvoiceByAmanpay(transactionId)
    if (!invoice || invoice.status === 'PAID') return
    await handlePaymentFailure(invoice.subscriptionId, invoice.id, status, event.failure_reason)
  }
}

async function applyPaypalWebhook(event: any) {
  if (event.event_type === 'PAYMENT.CAPTURE.COMPLETED') {
    const captureId = event.resource?.id as string
    const invoice   = await repo.findInvoiceByPaypal(captureId)
    if (!invoice || invoice.status === 'PAID') return  // idempotent
    await handlePaymentSuccess(invoice.subscriptionId, invoice.id)
  } else if (event.event_type === 'PAYMENT.CAPTURE.DENIED') {
    const captureId = event.resource?.id as string
    const invoice   = await repo.findInvoiceByPaypal(captureId)
    if (!invoice || invoice.status === 'PAID') return
    await handlePaymentFailure(invoice.subscriptionId, invoice.id, 'capture_denied')
  }
}

export async function handleAmanpayWebhook(event: any, rawBody: string | Buffer = JSON.stringify(event)) {
  return processWebhookOnce({
    provider: 'amanpay:subscriptions',
    providerEventId: getWebhookEventId('amanpay', event),
    eventType: String(event.status ?? 'unknown'),
    rawBody,
    handle: () => applyAmanpayWebhook(event),
  })
}

export async function handlePaypalWebhook(event: any, rawBody: string | Buffer = JSON.stringify(event)) {
  return processWebhookOnce({
    provider: 'paypal:subscriptions',
    providerEventId: getWebhookEventId('paypal', event),
    eventType: String(event.event_type ?? 'unknown'),
    rawBody,
    handle: () => applyPaypalWebhook(event),
  })
}

// ─── Checkout ─────────────────────────────────────────────────

export async function checkout(companyId: string, body: {
  plan: 'STARTER' | 'GROWTH' | 'PRO'; billingPeriod: 'MONTHLY' | 'ANNUAL'
  currency: 'MAD'; provider: 'AMANPAY' | 'PAYPAL'
  successUrl: string; failureUrl: string
}) {
  const dbPrice = await prisma.pricingConfig.findUnique({
    where: { plan_billingPeriod: { plan: body.plan, billingPeriod: body.billingPeriod } },
  })
  const amount = dbPrice?.amount ?? (PLAN_PRICES[body.plan]?.[body.billingPeriod] as any)?.[body.currency]
  if (!amount) throw new ValidationError('Invalid plan or billing period')

  const company      = await prisma.company.findUniqueOrThrow({ where: { id: companyId } })
  const subscription = await repo.findOrCreateSubscription(companyId, body.plan, body.billingPeriod, body.currency)

  const orderId     = `sub-${companyId}-${Date.now()}`
  const description = `${body.plan} plan — ${body.billingPeriod}`
  const webhookBase = process.env.API_URL ?? 'http://localhost:4000'

  let checkoutUrl: string
  let amanpayTransactionId: string | null = null
  let paypalCaptureId: string | null       = null

  if (body.provider === 'AMANPAY') {
    if (!amanpay.isConfigured()) throw new ValidationError('AmanPay is not configured on this platform')
    const result = await amanpay.createCheckout({
      amount, currency: body.currency, orderId, description,
      customerEmail: company.email, customerName: company.name,
      successUrl: body.successUrl, failureUrl: body.failureUrl,
      webhookUrl: `${webhookBase}/api/v1/subscriptions/webhooks/amanpay`,
    })
    checkoutUrl          = result.checkoutUrl
    amanpayTransactionId = result.transactionId
  } else {
    if (!paypal.isConfigured()) throw new ValidationError('PayPal is not configured on this platform')
    const result = await paypal.createOrder({
      amount, currency: body.currency, orderId, description,
      returnUrl: body.successUrl, cancelUrl: body.failureUrl,
    })
    checkoutUrl     = result.approveUrl
    paypalCaptureId = result.orderId
  }

  const dueAt = new Date(Date.now() + SUBSCRIPTION_POLICY.payment.paymentPendingTimeoutDays * 24 * 60 * 60 * 1000)
  const invoice = await repo.createInvoice({
    companyId, subscriptionId: subscription.id, amount, currency: body.currency,
    paymentProvider: body.provider, amanpayTransactionId, paypalCaptureId, dueAt,
  })
  return { invoice, checkoutUrl }
}

export async function capturePaypal(companyId: string, paypalOrderId: string) {
  const invoice  = await repo.findInvoiceByPaypalForCompany(paypalOrderId, companyId)
  if (invoice.status === 'PAID') return { success: true }  // idempotent
  const capture  = await paypal.captureOrder(paypalOrderId) as Record<string, any>
  const captureId = capture.purchase_units?.[0]?.payments?.captures?.[0]?.id ?? paypalOrderId
  await repo.updateInvoicePaypal(invoice.id, captureId)
  const periodEnd = addPeriod(new Date(), invoice.subscription.billingPeriod)
  await repo.activateSubscription(invoice.subscriptionId, periodEnd)
  await repo.createEvent({
    subscriptionId: invoice.subscriptionId,
    companyId,
    eventType: 'subscription.activated',
    source: 'user',
    payload: { invoiceId: invoice.id },
  })
  return { success: true }
}

// ─── Plan changes ─────────────────────────────────────────────

export function changePlan(companyId: string, data: { plan: any; billingPeriod: any; currency: string }) {
  return repo.updatePlan(companyId, data)
}

// ─── Cancellation ─────────────────────────────────────────────

export async function cancel(companyId: string, mode: 'period_end' | 'immediate' = 'period_end', reason?: string) {
  const sub = await repo.findByCompany(companyId)
  if (!sub) throw new ValidationError('No subscription found')

  const updated = await repo.setCancelled(sub.id, mode === 'immediate')
  await repo.createEvent({
    subscriptionId: sub.id,
    companyId,
    eventType: 'subscription.canceled',
    source: 'user',
    payload: { mode, reason: reason ?? null },
  })
  return updated
}

export async function resume(companyId: string) {
  const sub = await repo.findByCompany(companyId)
  if (!sub) throw new ValidationError('No subscription found')

  const updated = await repo.setCancelAtPeriodEnd(companyId, false)
  await repo.createEvent({
    subscriptionId: sub.id,
    companyId,
    eventType: 'subscription.resumed',
    source: 'user',
    payload: {},
  })
  return updated
}

// ─── Reactivation ────────────────────────────────────────────

export async function reactivate(companyId: string, body: {
  plan: 'STARTER' | 'GROWTH' | 'PRO'; billingPeriod: 'MONTHLY' | 'ANNUAL'
  currency: 'MAD'; provider: 'AMANPAY' | 'PAYPAL'
  successUrl: string; failureUrl: string
}) {
  const sub = await repo.findByCompany(companyId)
  if (!sub) throw new ValidationError('No subscription found')

  const allowedStatuses = ['CANCELLED', 'EXPIRED', 'SUSPENDED', 'PAST_DUE', 'PAYMENT_PENDING']
  if (!allowedStatuses.includes(sub.status)) {
    throw new ValidationError(`Cannot reactivate a subscription with status ${sub.status}`)
  }

  await repo.setPaymentPending(sub.id)
  await repo.createEvent({
    subscriptionId: sub.id,
    companyId,
    eventType: 'subscription.reactivated',
    source: 'user',
    payload: { plan: body.plan, billingPeriod: body.billingPeriod },
  })

  return checkout(companyId, body)
}

// ─── Scheduled job actions ────────────────────────────────────

export async function runTrialExpirationJob() {
  const expired = await repo.findTrialsEndedWithoutConversion()
  for (const sub of expired) {
    await repo.setExpired(sub.id)
    await repo.createEvent({
      subscriptionId: sub.id,
      companyId: sub.companyId,
      eventType: 'trial.expired',
      payload: { trialEndAt: sub.trialEndAt },
    })
  }
  return expired.length
}

export async function runPaymentPendingTimeoutJob() {
  const cutoff = new Date(
    Date.now() - SUBSCRIPTION_POLICY.payment.paymentPendingTimeoutDays * 24 * 60 * 60 * 1000,
  )
  const subs = await repo.findPaymentPendingTimedOut(cutoff)
  for (const sub of subs) {
    await repo.setPastDue(sub.id)
    await repo.createEvent({
      subscriptionId: sub.id,
      companyId: sub.companyId,
      eventType: 'subscription.past_due',
      payload: { paymentPendingSince: sub.paymentPendingSince },
    })
  }
  return subs.length
}

export async function runPastDueTimeoutJob() {
  const cutoff = new Date(
    Date.now() - SUBSCRIPTION_POLICY.pastDue.timeoutDays * 24 * 60 * 60 * 1000,
  )
  const subs = await repo.findPastDueTimedOut(cutoff)
  for (const sub of subs) {
    await repo.setSuspended(sub.id)
    await repo.createEvent({
      subscriptionId: sub.id,
      companyId: sub.companyId,
      eventType: 'subscription.suspended',
      payload: { pastDueSince: sub.pastDueSince },
    })
  }
  return subs.length
}

export async function runSuspensionTimeoutJob() {
  const cutoff = new Date(
    Date.now() - SUBSCRIPTION_POLICY.suspension.cancelAfterDays * 24 * 60 * 60 * 1000,
  )
  const subs = await repo.findSuspendedTimedOut(cutoff)
  for (const sub of subs) {
    await repo.setCancelled(sub.id, true)
    await repo.createEvent({
      subscriptionId: sub.id,
      companyId: sub.companyId,
      eventType: 'subscription.canceled',
      payload: { reason: 'suspension_timeout', suspendedAt: sub.suspendedAt },
    })
  }
  return subs.length
}

export async function runPeriodEndCancellationJob() {
  const subs = await repo.findSubscriptionsToExpireAtPeriodEnd()
  for (const sub of subs) {
    await repo.setCancelled(sub.id, true)
    await repo.createEvent({
      subscriptionId: sub.id,
      companyId: sub.companyId,
      eventType: 'subscription.canceled',
      payload: { reason: 'period_end', currentPeriodEnd: sub.currentPeriodEnd },
    })
  }
  return subs.length
}

// ─── Admin overrides ──────────────────────────────────────────

export async function adminExtendTrial(
  subscriptionId: string,
  extraDays: number,
  adminId: string,
  reason: string,
) {
  const sub = await repo.findById(subscriptionId)
  if (!sub) throw new ValidationError('Subscription not found')

  const currentEnd = sub.trialEndAt ?? new Date()
  const newEnd = new Date(currentEnd.getTime() + extraDays * 24 * 60 * 60 * 1000)

  await prisma.subscription.update({
    where: { id: subscriptionId },
    data: { trialEndAt: newEnd },
  })
  await repo.createEvent({
    subscriptionId,
    companyId: sub.companyId,
    eventType: 'admin.override_created',
    source: 'admin',
    payload: { action: 'extend_trial', extraDays, newEnd, adminId, reason },
  })
  return { trialEndAt: newEnd }
}

export async function adminExtendGracePeriod(
  subscriptionId: string,
  extraDays: number,
  adminId: string,
  reason: string,
) {
  const sub = await repo.findById(subscriptionId)
  if (!sub) throw new ValidationError('Subscription not found')

  await repo.setPaymentPending(sub.id)
  await repo.createEvent({
    subscriptionId,
    companyId: sub.companyId,
    eventType: 'admin.override_created',
    source: 'admin',
    payload: { action: 'extend_grace_period', extraDays, adminId, reason },
  })
  return { success: true }
}

export async function adminCancelSubscription(
  subscriptionId: string,
  adminId: string,
  reason: string,
) {
  const sub = await repo.findById(subscriptionId)
  if (!sub) throw new ValidationError('Subscription not found')

  await repo.setCancelled(sub.id, true)
  await repo.createEvent({
    subscriptionId,
    companyId: sub.companyId,
    eventType: 'subscription.canceled',
    source: 'admin',
    payload: { adminId, reason },
  })
  return { success: true }
}

export async function adminReactivateSubscription(
  subscriptionId: string,
  adminId: string,
  reason: string,
) {
  const sub = await repo.findById(subscriptionId)
  if (!sub) throw new ValidationError('Subscription not found')

  const periodEnd = addPeriod(new Date(), sub.billingPeriod)
  await repo.activateSubscription(subscriptionId, periodEnd)
  await repo.createEvent({
    subscriptionId,
    companyId: sub.companyId,
    eventType: 'subscription.reactivated',
    source: 'admin',
    payload: { adminId, reason, periodEnd },
  })
  return { success: true }
}

export async function adminSuspendSubscription(
  subscriptionId: string,
  adminId: string,
  reason: string,
) {
  const sub = await repo.findById(subscriptionId)
  if (!sub) throw new ValidationError('Subscription not found')

  await repo.setSuspended(sub.id)
  await repo.createEvent({
    subscriptionId,
    companyId: sub.companyId,
    eventType: 'subscription.suspended',
    source: 'admin',
    payload: { adminId, reason },
  })
  return { success: true }
}
