package warehousedeposit

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"
)

var (
	ErrInvalidInput      = errors.New("input tidak valid")
	ErrDepositNotPending = errors.New("setoran sudah diterima atau tidak dalam status pending")
	ErrWarehouseRequired = errors.New("gudang wajib dipilih")
	ErrDriverRequired    = errors.New("driver wajib dipilih")
	ErrPaymentsRequired  = errors.New("pilih minimal satu pembayaran untuk disetor")
)

type Sequencer func(ctx context.Context, prefix string, t time.Time) (int64, error)

type Service struct {
	store Store
	seq   Sequencer
	loc   *time.Location
}

func NewService(store Store, seq Sequencer, loc *time.Location) *Service {
	if loc == nil {
		loc = time.UTC
	}
	return &Service{
		store: store,
		seq:   seq,
		loc:   loc,
	}
}

// Create builds a Setoran from a checklist of already-recorded cash/giro
// payments (in.PaymentIDs) — the amount is always their sum, computed inside
// Store.CreateWithPayments, never typed by the caller. DriverID is optional:
// nil covers a direct/ad-hoc deposit (e.g. Sales handing over cash a
// customer paid them directly, with no driver run involved).
func (s *Service) Create(ctx context.Context, in CreateInput, userID *int64) (*WarehouseDeposit, error) {
	if in.WarehouseID <= 0 {
		return nil, ErrWarehouseRequired
	}
	if len(in.PaymentIDs) == 0 {
		return nil, ErrPaymentsRequired
	}
	var driverID *int64
	var driverName string
	if in.DriverID != nil && *in.DriverID > 0 {
		// Driver name is always resolved server-side from the driver master
		// data rather than trusted from client input — it used to be free text.
		name, err := s.store.DriverName(ctx, *in.DriverID)
		if err != nil {
			return nil, ErrDriverRequired
		}
		id := *in.DriverID
		driverID = &id
		driverName = name
	}
	today := time.Now().In(s.loc)
	if in.DepositDate == "" {
		in.DepositDate = today.Format("2006-01-02")
	}

	depNumber := fmt.Sprintf("WD-%s-%04d", today.Format("20060102"), time.Now().Unix()%10000)
	if s.seq != nil {
		n, err := s.seq(ctx, "wd", today)
		if err == nil {
			depNumber = fmt.Sprintf("WD-%s-%04d", today.Format("20060102"), n)
		}
	}

	d := &WarehouseDeposit{
		DepositNumber: depNumber,
		WarehouseID:   in.WarehouseID,
		DriverID:      driverID,
		DriverName:    driverName,
		DepositDate:   in.DepositDate,
		Notes:         strings.TrimSpace(in.Notes),
		Status:        "pending",
		CreatedBy:     userID,
	}

	if err := s.store.CreateWithPayments(ctx, d, in.PaymentIDs); err != nil {
		return nil, err
	}
	return s.store.GetByID(ctx, d.ID)
}

func (s *Service) List(ctx context.Context, f Filter) ([]WarehouseDeposit, int, error) {
	return s.store.List(ctx, f)
}

func (s *Service) Get(ctx context.Context, id int64) (*WarehouseDeposit, error) {
	return s.store.GetByID(ctx, id)
}

// UndepositedPayments is the "Catat Setoran" checklist source — see
// Store.ListUndepositedPayments.
func (s *Service) UndepositedPayments(ctx context.Context, f UndepositedFilter) ([]UndepositedPayment, error) {
	return s.store.ListUndepositedPayments(ctx, f)
}

// Receive is a Kasir Pusat's custody confirmation — no shift/drawer/cashflow
// involved (see WarehouseDeposit doc comment): the cash was already
// recognized in the books when the underlying payment(s) were recorded, so
// this just flags that it has physically reached the cashier.
func (s *Service) Receive(ctx context.Context, id int64, receivedByUserID int64) (*WarehouseDeposit, error) {
	d, err := s.store.GetByIDForUpdate(ctx, id)
	if err != nil {
		return nil, err
	}
	if d.Status != "pending" {
		return nil, ErrDepositNotPending
	}

	now := time.Now().In(s.loc)
	if err := s.store.UpdateReceived(ctx, d.ID, receivedByUserID, now); err != nil {
		return nil, err
	}

	return s.store.GetByID(ctx, d.ID)
}
