import { eq, and, gte, lt } from 'drizzle-orm'
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import { transactions, drawerSessions, drawerAdjustments, registers } from '../db/schema/pos.js'
import { locations } from '../db/schema/stores.js'
import { users } from '../db/schema/users.js'
import { NotFoundError } from '../lib/errors.js'

interface PaymentBreakdown {
  count: number
  total: number
}

interface DrawerReport {
  session: {
    id: string
    openedAt: string
    closedAt: string | null
    openingBalance: string
    closingBalance: string | null
    expectedBalance: string | null
    overShort: string | null
    status: string
    notes: string | null
    denominations: Record<string, number> | null
    register: { id: string; name: string } | null
    openedBy: { id: string; firstName: string; lastName: string } | null
    closedBy: { id: string; firstName: string; lastName: string } | null
  }
  sales: {
    grossSales: number
    netSales: number
    transactionCount: number
    voidCount: number
    refundTotal: number
  }
  payments: Record<string, PaymentBreakdown>
  discounts: {
    total: number
    count: number
  }
  cash: {
    openingBalance: number
    cashSales: number
    cashIn: number
    cashOut: number
    expectedBalance: number
    actualBalance: number | null
    overShort: number | null
  }
  adjustments: { id: string; type: string; amount: string; reason: string; createdAt: string }[]
}

export const ReportService = {
  async getDrawerReport(db: PostgresJsDatabase<any>, drawerSessionId: string): Promise<DrawerReport> {
    // Fetch session with register and user info
    const [session] = await db
      .select()
      .from(drawerSessions)
      .where(eq(drawerSessions.id, drawerSessionId))
      .limit(1)

    if (!session) throw new NotFoundError('Drawer session')

    // Fetch register info
    let register: { id: string; name: string } | null = null
    if (session.registerId) {
      const [reg] = await db.select({ id: registers.id, name: registers.name }).from(registers).where(eq(registers.id, session.registerId)).limit(1)
      register = reg ?? null
    }

    // Fetch user info
    const [openedByUser] = await db.select({ id: users.id, firstName: users.firstName, lastName: users.lastName }).from(users).where(eq(users.id, session.openedBy)).limit(1)
    let closedByUser = null
    if (session.closedBy) {
      const [u] = await db.select({ id: users.id, firstName: users.firstName, lastName: users.lastName }).from(users).where(eq(users.id, session.closedBy)).limit(1)
      closedByUser = u ?? null
    }

    // Aggregate transaction data for this drawer session
    const txns = await db
      .select({
        status: transactions.status,
        transactionType: transactions.transactionType,
        paymentMethod: transactions.paymentMethod,
        total: transactions.total,
        discountTotal: transactions.discountTotal,
        roundingAdjustment: transactions.roundingAdjustment,
      })
      .from(transactions)
      .where(eq(transactions.drawerSessionId, drawerSessionId))

    // Calculate sales
    let grossSales = 0
    let refundTotal = 0
    let transactionCount = 0
    let voidCount = 0
    let discountTotalSum = 0
    let discountCount = 0
    const payments: Record<string, PaymentBreakdown> = {}

    for (const txn of txns) {
      if (txn.status === 'voided') {
        voidCount++
        continue
      }
      if (txn.status !== 'completed') continue

      const total = parseFloat(txn.total ?? '0')
      const discAmt = parseFloat(txn.discountTotal ?? '0')

      if (txn.transactionType === 'refund') {
        refundTotal += total
      } else {
        grossSales += total
        transactionCount++
      }

      if (discAmt > 0) {
        discountTotalSum += discAmt
        discountCount++
      }

      const method = txn.paymentMethod ?? 'unknown'
      if (!payments[method]) payments[method] = { count: 0, total: 0 }
      payments[method].count++
      payments[method].total += total
    }

    // Cash accountability
    const cashPayment = payments['cash'] ?? { count: 0, total: 0 }
    const cashRounding = txns
      .filter((t) => t.status === 'completed' && t.paymentMethod === 'cash')
      .reduce((sum, t) => sum + parseFloat(t.roundingAdjustment ?? '0'), 0)
    const cashSales = cashPayment.total + cashRounding

    // Adjustments
    const adjRows = await db
      .select()
      .from(drawerAdjustments)
      .where(eq(drawerAdjustments.drawerSessionId, drawerSessionId))

    let cashIn = 0
    let cashOut = 0
    for (const adj of adjRows) {
      if (adj.type === 'cash_in') cashIn += parseFloat(adj.amount)
      else cashOut += parseFloat(adj.amount)
    }

    const openingBalance = parseFloat(session.openingBalance)
    const expectedBalance = openingBalance + cashSales + cashIn - cashOut
    const actualBalance = session.closingBalance ? parseFloat(session.closingBalance) : null
    const overShort = actualBalance !== null ? Math.round((actualBalance - expectedBalance) * 100) / 100 : null

    return {
      session: {
        id: session.id,
        openedAt: session.openedAt.toISOString(),
        closedAt: session.closedAt?.toISOString() ?? null,
        openingBalance: session.openingBalance,
        closingBalance: session.closingBalance,
        expectedBalance: session.expectedBalance,
        overShort: session.overShort,
        status: session.status,
        notes: session.notes,
        denominations: session.denominations,
        register,
        openedBy: openedByUser ?? null,
        closedBy: closedByUser,
      },
      sales: {
        grossSales: Math.round(grossSales * 100) / 100,
        netSales: Math.round((grossSales - refundTotal) * 100) / 100,
        transactionCount,
        voidCount,
        refundTotal: Math.round(refundTotal * 100) / 100,
      },
      payments,
      discounts: {
        total: Math.round(discountTotalSum * 100) / 100,
        count: discountCount,
      },
      cash: {
        openingBalance,
        cashSales: Math.round(cashSales * 100) / 100,
        cashIn: Math.round(cashIn * 100) / 100,
        cashOut: Math.round(cashOut * 100) / 100,
        expectedBalance: Math.round(expectedBalance * 100) / 100,
        actualBalance,
        overShort,
      },
      adjustments: adjRows.map((a) => ({
        id: a.id,
        type: a.type,
        amount: a.amount,
        reason: a.reason,
        createdAt: a.createdAt.toISOString(),
      })),
    }
  },

  async getDailyReport(db: PostgresJsDatabase<any>, locationId: string, date: string) {
    // Get location info
    const [location] = await db
      .select({ id: locations.id, name: locations.name, timezone: locations.timezone })
      .from(locations)
      .where(eq(locations.id, locationId))
      .limit(1)

    if (!location) throw new NotFoundError('Location')

    // Find all drawer sessions opened at this location on the given date
    const dayStart = new Date(`${date}T00:00:00`)
    const dayEnd = new Date(`${date}T00:00:00`)
    dayEnd.setDate(dayEnd.getDate() + 1)

    const sessions = await db
      .select()
      .from(drawerSessions)
      .where(and(
        eq(drawerSessions.locationId, locationId),
        gte(drawerSessions.openedAt, dayStart),
        lt(drawerSessions.openedAt, dayEnd),
      ))

    // Get individual reports for each session
    const sessionReports = await Promise.all(
      sessions.map((s) => this.getDrawerReport(db, s.id))
    )

    // Aggregate
    const sales = { grossSales: 0, netSales: 0, transactionCount: 0, voidCount: 0, refundTotal: 0 }
    const payments: Record<string, PaymentBreakdown> = {}
    const discounts = { total: 0, count: 0 }
    const cash = { totalOpening: 0, totalCashSales: 0, totalCashIn: 0, totalCashOut: 0, totalExpected: 0, totalActual: 0, totalOverShort: 0 }

    for (const report of sessionReports) {
      sales.grossSales += report.sales.grossSales
      sales.netSales += report.sales.netSales
      sales.transactionCount += report.sales.transactionCount
      sales.voidCount += report.sales.voidCount
      sales.refundTotal += report.sales.refundTotal

      for (const [method, data] of Object.entries(report.payments)) {
        if (!payments[method]) payments[method] = { count: 0, total: 0 }
        payments[method].count += data.count
        payments[method].total += data.total
      }

      discounts.total += report.discounts.total
      discounts.count += report.discounts.count

      cash.totalOpening += report.cash.openingBalance
      cash.totalCashSales += report.cash.cashSales
      cash.totalCashIn += report.cash.cashIn
      cash.totalCashOut += report.cash.cashOut
      cash.totalExpected += report.cash.expectedBalance
      if (report.cash.actualBalance !== null) cash.totalActual += report.cash.actualBalance
      if (report.cash.overShort !== null) cash.totalOverShort += report.cash.overShort
    }

    // Round all aggregated values
    for (const key of Object.keys(sales) as (keyof typeof sales)[]) {
      sales[key] = Math.round(sales[key] * 100) / 100
    }
    for (const data of Object.values(payments)) {
      data.total = Math.round(data.total * 100) / 100
    }
    discounts.total = Math.round(discounts.total * 100) / 100
    for (const key of Object.keys(cash) as (keyof typeof cash)[]) {
      cash[key] = Math.round(cash[key] * 100) / 100
    }

    return {
      date,
      location: { id: location.id, name: location.name },
      sessions: sessionReports.map((r) => ({
        id: r.session.id,
        register: r.session.register,
        openedBy: r.session.openedBy,
        openedAt: r.session.openedAt,
        closedAt: r.session.closedAt,
        status: r.session.status,
        overShort: r.cash.overShort,
        grossSales: r.sales.grossSales,
      })),
      sales,
      payments,
      discounts,
      cash,
    }
  },
}
