package cashier

import (
	"context"
	"errors"

	"github.com/jackc/pgx/v5/pgtype"
	"github.com/shopspring/decimal"

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"github.com/TaruLab/keluargamart-backend/internal/db"
)

// RecordCashRefund implements sales.RefundDrawer: a cash refund leaves the
// drawer as a cash_out entry so the shift's expected-cash stays truthful.
// The caller transaction must retain the shift lock through its refund commit.
func (r *Repository) RecordCashRefund(ctx context.Context, q db.DBTX, shiftID int64, amount decimal.Decimal, notes string) error {
	if q == nil {
		return ErrForbidden
	}
	if !amount.IsPositive() {
		return errors.New("refund amount must be positive")
	}
	if !amount.Equal(amount.Round(2)) {
		return errors.New("refund amount must have at most 2 decimal places")
	}
	row, err := r.GetShiftByIDForUpdate(ctx, q, shiftID)
	if err != nil {
		return err
	}
	if !auth.StoreScopeFrom(ctx).Allows(int8ToPtr(row.StoreID)) {
		return ErrNotFound
	}
	// The shift's own cashier normally records the refund; admin/owner may
	// also record one on the cashier's behalf (the POS void path does
	// exactly that — 000151). canOverride is the shared permission gate.
	if err := requireCashier(ctx, row.CashierID); err != nil {
		if !canOverride(ctx) {
			return err
		}
	}
	if row.Status != "open" {
		return ErrShiftNotOpen
	}
	actorID := auth.UserFrom(ctx).ID
	_, err = r.CreateDrawerEntry(ctx, q, db.CreateDrawerEntryParams{
		ShiftID:   shiftID,
		Type:      "cash_out",
		Amount:    amount,
		Notes:     pgtype.Text{String: notes, Valid: true},
		CreatedBy: pgtype.Int8{Int64: actorID, Valid: true},
	})
	return err
}

// LockOpenShift serializes every financial mutation with closure.
func (r *Repository) LockOpenShift(ctx context.Context, q db.DBTX, shiftID int64) error {
	if q == nil {
		return ErrForbidden
	}
	row, err := r.GetShiftByIDForUpdate(ctx, q, shiftID)
	if err != nil {
		return err
	}
	if !row.StoreID.Valid {
		return errors.New("shift is not bound to a store")
	}
	active, err := r.queries(q).IsStoreActive(ctx, row.StoreID.Int64)
	if err != nil {
		return err
	}
	if !active {
		return errors.New("shift store is inactive or not found")
	}
	if !auth.StoreScopeFrom(ctx).Allows(int8ToPtr(row.StoreID)) {
		return ErrNotFound
	}
	if err := requireCashier(ctx, row.CashierID); err != nil {
		return err
	}
	if row.Status != "open" {
		return ErrShiftNotOpen
	}
	return nil
}
