import { eq, and, count, sum, sql, type Column } from 'drizzle-orm'
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import { drawerSessions, drawerAdjustments, transactions } from '../db/schema/pos.js'
import { ConflictError, NotFoundError } from '../lib/errors.js'
import type { DrawerOpenInput, DrawerCloseInput, DrawerAdjustmentInput, PaginationInput } from '@lunarfront/shared/schemas'
import { withPagination, withSort, paginatedResponse } from '../utils/pagination.js'

export const DrawerService = {
  async open(db: PostgresJsDatabase<any>, input: DrawerOpenInput, openedBy: string) {
    // Ensure no other open session at this location
    if (input.locationId) {
      const existing = await this.getOpen(db, input.locationId)
      if (existing) {
        throw new ConflictError('A drawer session is already open at this location')
      }
    }

    const [session] = await db
      .insert(drawerSessions)
      .values({
        locationId: input.locationId,
        registerId: input.registerId,
        openedBy,
        openingBalance: input.openingBalance.toString(),
      })
      .returning()
    return session
  },

  async close(db: PostgresJsDatabase<any>, sessionId: string, input: DrawerCloseInput, closedBy: string) {
    const session = await this.getById(db, sessionId)
    if (!session) throw new NotFoundError('Drawer session')
    if (session.status === 'closed') throw new ConflictError('Drawer session is already closed')

    // Calculate expected balance from cash transactions in this drawer session
    // Net cash kept = total + rounding_adjustment (change is already accounted for)
    const [cashTotals] = await db
      .select({
        total: sum(transactions.total),
        rounding: sum(transactions.roundingAdjustment),
      })
      .from(transactions)
      .where(
        and(
          eq(transactions.drawerSessionId, sessionId),
          eq(transactions.status, 'completed'),
          eq(transactions.paymentMethod, 'cash')
        )
      )

    // Calculate net drawer adjustments (cash_in adds, cash_out subtracts)
    const [adjTotals] = await db
      .select({
        cashIn: sql<string>`coalesce(sum(case when ${drawerAdjustments.type} = 'cash_in' then ${drawerAdjustments.amount} else 0 end), 0)`,
        cashOut: sql<string>`coalesce(sum(case when ${drawerAdjustments.type} = 'cash_out' then ${drawerAdjustments.amount} else 0 end), 0)`,
      })
      .from(drawerAdjustments)
      .where(eq(drawerAdjustments.drawerSessionId, sessionId))

    const salesCashIn = parseFloat(cashTotals?.total ?? '0') + parseFloat(cashTotals?.rounding ?? '0')
    const adjCashIn = parseFloat(adjTotals?.cashIn ?? '0')
    const adjCashOut = parseFloat(adjTotals?.cashOut ?? '0')
    const openingBalance = parseFloat(session.openingBalance)
    const expectedBalance = openingBalance + salesCashIn + adjCashIn - adjCashOut
    const closingBalance = input.closingBalance
    const overShort = closingBalance - expectedBalance

    const [updated] = await db
      .update(drawerSessions)
      .set({
        closedBy,
        closingBalance: closingBalance.toString(),
        expectedBalance: expectedBalance.toString(),
        overShort: overShort.toString(),
        denominations: input.denominations,
        notes: input.notes,
        status: 'closed',
        closedAt: new Date(),
      })
      .where(eq(drawerSessions.id, sessionId))
      .returning()
    return updated
  },

  async getOpen(db: PostgresJsDatabase<any>, locationId: string) {
    const [session] = await db
      .select()
      .from(drawerSessions)
      .where(
        and(
          eq(drawerSessions.locationId, locationId),
          eq(drawerSessions.status, 'open')
        )
      )
      .limit(1)
    return session ?? null
  },

  async getById(db: PostgresJsDatabase<any>, id: string) {
    const [session] = await db
      .select()
      .from(drawerSessions)
      .where(eq(drawerSessions.id, id))
      .limit(1)
    return session ?? null
  },

  async addAdjustment(db: PostgresJsDatabase<any>, sessionId: string, input: DrawerAdjustmentInput, createdBy: string, _approvedBy?: string) {
    const session = await this.getById(db, sessionId)
    if (!session) throw new NotFoundError('Drawer session')
    if (session.status === 'closed') throw new ConflictError('Cannot adjust a closed drawer')

    const [adjustment] = await db
      .insert(drawerAdjustments)
      .values({
        drawerSessionId: sessionId,
        type: input.type,
        amount: input.amount.toString(),
        reason: input.reason,
        createdBy,
      })
      .returning()
    return adjustment
  },

  async getAdjustments(db: PostgresJsDatabase<any>, sessionId: string) {
    return db
      .select()
      .from(drawerAdjustments)
      .where(eq(drawerAdjustments.drawerSessionId, sessionId))
  },

  async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
    const sortableColumns: Record<string, Column> = {
      opened_at: drawerSessions.openedAt,
      closed_at: drawerSessions.closedAt,
      status: drawerSessions.status,
    }

    let query = db.select().from(drawerSessions).$dynamic()
    query = withSort(query, params.sort, params.order, sortableColumns, drawerSessions.openedAt)
    query = withPagination(query, params.page, params.limit)

    const [data, [{ total }]] = await Promise.all([
      query,
      db.select({ total: count() }).from(drawerSessions),
    ])

    return paginatedResponse(data, total, params.page, params.limit)
  },
}
