// Package purchasing implements purchase orders, goods receipts, supplier
// debt + payDebt and vouchers (business-rules §4). PO tax is integer-rounded
// (PHP round half-up); GR applies a raw stock increment (no lock, no movement)
// for parity. Codes: PO/GR/PV/CF-YYYYMMDD-<4> (§1).
package purchasing

import (
	"context"
	"time"

	"github.com/shopspring/decimal"

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

// PurchaseOrder mirrors purchase_orders.
type PurchaseOrder struct {
	ID              int64           `json:"id"`
	PoNumber        string          `json:"po_number"`
	SupplierID      int64           `json:"supplier_id"`
	WarehouseID     int64           `json:"warehouse_id"`
	CreatedBy       *int64          `json:"created_by"`
	OrderDate       string          `json:"order_date"`
	ExpectedDate    *string         `json:"expected_date"`
	DueDate         *string         `json:"due_date"`
	Status          string          `json:"status"`
	Subtotal        decimal.Decimal `json:"subtotal"`
	TaxAmount       decimal.Decimal `json:"tax_amount"`
	DiscountAmount  decimal.Decimal `json:"discount_amount"`
	TotalAmount     decimal.Decimal `json:"total_amount"`
	PaidAmount      decimal.Decimal `json:"paid_amount"`
	RemainingAmount decimal.Decimal `json:"remaining_amount"`
	Notes           *string         `json:"notes"`
	Items           []POItem        `json:"items,omitempty"`
	SupplierName    string          `json:"supplier_name,omitempty"`
	WarehouseName   string          `json:"warehouse_name,omitempty"`
	// Print/document enrichment (client feedback 2026-08-26 section 1).
	SupplierAddress *string    `json:"supplier_address,omitempty"`
	SupplierPhone   *string    `json:"supplier_phone,omitempty"`
	SupplierEmail   *string    `json:"supplier_email,omitempty"`
	CreatedByName   string     `json:"created_by_name,omitempty"`
	ApprovedByName  string     `json:"approved_by_name,omitempty"`
	ApprovedAt      *time.Time `json:"approved_at,omitempty"`
	CreatedAt       time.Time  `json:"created_at"`
	UpdatedAt       time.Time  `json:"updated_at"`

	// Print-once ledger (000150): count > 0 means the signed-approval sheet
	// has been produced; printed_at/by record the LAST print. Reprints are
	// refused for ordinary callers (ErrAlreadyPrinted) — admin/owner may
	// reprint (bypass lives in the caller's decision, same as every other
	// permission check in this codebase).
	PrintCount     int        `json:"print_count"`
	PrintedAt      *time.Time `json:"printed_at,omitempty"`
	PrintedByName  string     `json:"printed_by_name,omitempty"`
}

// POItem mirrors purchase_order_items.
type POItem struct {
	ID               int64           `json:"id"`
	PurchaseOrderID  int64           `json:"purchase_order_id"`
	ProductID        int64           `json:"product_id"`
	Quantity         decimal.Decimal `json:"quantity"`
	ReceivedQuantity decimal.Decimal `json:"received_quantity"`
	UnitPrice        decimal.Decimal `json:"unit_price"`
	Discount         decimal.Decimal `json:"discount"`
	Subtotal         decimal.Decimal `json:"subtotal"`
	ProductCode      string          `json:"product_code,omitempty"`
	ProductName      string          `json:"product_name,omitempty"`
	UnitSymbol       string          `json:"unit_symbol,omitempty"`
}

// POFilter carries pagination/search for List.
type POFilter struct {
	Page       int
	PerPage    int
	Search     string
	Status     string
	SupplierID int64
	SortBy     string
	SortDir    string
}

// POInput mirrors the regular PO request body (§4.1).
type POInput struct {
	// CreatedBy is injected by the handler from the authenticated user.
	CreatedBy      *int64          `json:"-"`
	SupplierID     int64           `json:"supplier_id"`
	WarehouseID    int64           `json:"warehouse_id"`
	OrderDate      string          `json:"order_date"`
	ExpectedDate   *string         `json:"expected_date"`
	DueDate        *string         `json:"due_date"`
	TaxPercent     decimal.Decimal `json:"tax_percent"`
	DiscountAmount decimal.Decimal `json:"discount_amount"`
	Notes          *string         `json:"notes"`
	Items          []POItemInput   `json:"items"`
}

type POItemInput struct {
	ProductID int64           `json:"product_id"`
	Quantity  decimal.Decimal `json:"quantity"`
	UnitPrice decimal.Decimal `json:"unit_price"`
	Discount  decimal.Decimal `json:"discount"`
}

// GoodsReceipt mirrors goods_receipts.
type GoodsReceipt struct {
	ID              int64   `json:"id"`
	GrNumber        string  `json:"gr_number"`
	PurchaseOrderID int64   `json:"purchase_order_id"`
	WarehouseID     int64   `json:"warehouse_id"`
	ReceivedBy      *int64  `json:"received_by"`
	ReceivedDate    string  `json:"received_date"`
	Notes           *string `json:"notes"`
	Status          string  `json:"status"`
	// JournalNumber is the auto-drafted accrual journal (source linkage);
	// empty when the hook is unwired (tests) or drafting failed non-fatally.
	JournalNumber string    `json:"journal_number,omitempty"`
	Items         []GRItem  `json:"items,omitempty"`
	PONumber      string    `json:"po_number,omitempty"`
	SupplierName  string    `json:"supplier_name,omitempty"`
	WarehouseName string    `json:"warehouse_name,omitempty"`
	CreatedAt     time.Time `json:"created_at"`
}

// GRItem mirrors goods_receipt_items.
type GRItem struct {
	ID                  int64           `json:"id"`
	GoodsReceiptID      int64           `json:"goods_receipt_id"`
	PurchaseOrderItemID int64           `json:"purchase_order_item_id"`
	ProductID           int64           `json:"product_id"`
	QuantityOrdered     decimal.Decimal `json:"quantity_ordered"`
	QuantityReceived    decimal.Decimal `json:"quantity_received"`
	QuantityRejected    decimal.Decimal `json:"quantity_rejected"`
	DefectNotes         *string         `json:"defect_notes"`
	BatchNumber         *string         `json:"batch_number"`
	ExpiredDate         *string         `json:"expired_date"`
	ProductCode         string          `json:"product_code,omitempty"`
	ProductName         string          `json:"product_name,omitempty"`
}

// GRInput mirrors the GR request body (§4.3).
type GRInput struct {
	PurchaseOrderID int64         `json:"purchase_order_id"`
	WarehouseID     int64         `json:"warehouse_id"`
	ReceivedDate    string        `json:"received_date"`
	Notes           *string       `json:"notes"`
	Items           []GRItemInput `json:"items"`
	// DepartmentCode stamps the cost center (Kode Pabrik/Depo) on the
	// auto-drafted accrual journal lines; empty = first active department.
	DepartmentCode string `json:"department_code"`
}

type GRFilter struct {
	Page    int
	PerPage int
	Status  string
}

type GRItemInput struct {
	ProductID        int64           `json:"product_id"`
	QuantityReceived decimal.Decimal `json:"quantity_received"`
	// QuantityRejected: barang cacat / ditolak pada penerimaan ini. Selama ini
	// frontend mengirimnya dan backend mengabaikan — PO tetap berutang penuh
	// atas barang yang tidak pernah diterima. Menurunkan sisa tagihan.
	QuantityRejected decimal.Decimal `json:"quantity_rejected"`
	// DefectNotes: alasan penolakan (kemasan rusak, bocor, kadaluarsa, …).
	DefectNotes *string `json:"defect_notes"`
	BatchNumber *string `json:"batch_number"`
	ExpiredDate *string `json:"expired_date"`
}

// DebtRow is one line of the supplier debt list (§4.4).
type DebtRow struct {
	// Drill-down (input finance 1 Nov): komponen utang per PO.
	RejectedValue decimal.Decimal `json:"rejected_value"`
	RejectedLines int64           `json:"rejected_lines"`
	POID          int64           `json:"po_id"`
	PoNumber      string          `json:"po_number"`
	SupplierID    int64           `json:"supplier_id"`
	SupplierName  string          `json:"supplier_name"`
	OrderDate     string          `json:"order_date"`
	DueDate       *string         `json:"due_date"`
	Status        string          `json:"status"`
	TotalAmount   decimal.Decimal `json:"total_amount"`
	PaidAmount    decimal.Decimal `json:"paid_amount"`
	Remaining     decimal.Decimal `json:"remaining_amount"`
}

// DebtFilter carries filters for the debt list.
type DebtFilter struct {
	Page       int
	PerPage    int
	SupplierID int64
	Search     string
	DueDate    string
	DebtStatus string
	// ERP column sort (sort_by + sort_dir), whitelisted in ListDebts.
	SortBy  string
	SortDir string
}

type DebtSummary struct {
	TotalAmount     decimal.Decimal `json:"total_amount"`
	TotalPaid       decimal.Decimal `json:"total_paid"`
	TotalPO         decimal.Decimal `json:"total_po"`
	CountUnpaid     int64           `json:"debt_count"`
	ActiveSuppliers int64           `json:"active_suppliers"`
}

// Voucher mirrors vouchers (§4.6).
type Voucher struct {
	ID              int64           `json:"id"`
	VoucherNumber   string          `json:"voucher_number"`
	Type            string          `json:"type"`
	Description     *string         `json:"description"`
	VoucherDate     string          `json:"voucher_date"`
	Amount          decimal.Decimal `json:"amount"`
	Status          string          `json:"status"`
	CreatedBy       *int64          `json:"created_by"`
	SupplierID      *int64          `json:"supplier_id"`
	POID            *int64          `json:"po_id"`
	CashAccountCode string          `json:"cash_account_code"`
	PrintedAt       *time.Time      `json:"printed_at"`
	PrintedBy       *int64          `json:"printed_by"`
}

// VoucherInput mirrors the voucher create/update request body.
// Status accepts both backend values (available/used) and frontend aliases (pending/paid).
type VoucherInput struct {
	Description     *string         `json:"description"`
	VoucherDate     string          `json:"voucher_date"`
	Amount          decimal.Decimal `json:"amount"`
	Type            string          `json:"type"`
	Status          *string         `json:"status"`
	SupplierID      *int64          `json:"supplier_id"`
	POID            *int64          `json:"po_id"`
	CashAccountCode string          `json:"cash_account_code"`
}

type VoucherFilter struct {
	Page    int
	PerPage int
	Status  string
}

// Tx is a physical transaction and a db.DBTX for transaction-scoped queries.
type Tx interface {
	db.DBTX
	Commit(ctx context.Context) error
	Rollback(ctx context.Context) error
}

// Store is the purchasing sqlc-backed persistence seam.
type Store interface {
	Begin(ctx context.Context) (Tx, error)

	// purchase orders (writes use q for atomic use cases)
	CreateOrder(ctx context.Context, q db.DBTX, p db.CreatePurchaseOrderParams) (db.CreatePurchaseOrderRow, error)
	UpdateOrder(ctx context.Context, q db.DBTX, p db.UpdatePurchaseOrderParams) error
	DeleteOrder(ctx context.Context, q db.DBTX, id int64) error
	OrderByID(ctx context.Context, id int64) (db.GetPurchaseOrderByIDRow, error)
	OrderByIDForUpdate(ctx context.Context, q db.DBTX, id int64) (db.GetPurchaseOrderByIDForUpdateRow, error)
	StampPrint(ctx context.Context, p db.PurchaseOrdersStampPrintParams) (int64, error)
	ListOrders(ctx context.Context, f POFilter) ([]db.ListPurchaseOrdersRow, int, error)
	CreateItem(ctx context.Context, q db.DBTX, p db.CreatePurchaseOrderItemParams) (int64, error)
	ItemsByOrder(ctx context.Context, orderID int64) ([]db.ListPurchaseOrderItemsRow, error)
	ItemsByOrderForUpdate(ctx context.Context, q db.DBTX, orderID int64) ([]db.ListPurchaseOrderItemsForUpdateRow, error)
	DeleteItems(ctx context.Context, q db.DBTX, orderID int64) error
	SetOrderStatus(ctx context.Context, q db.DBTX, p db.SetPurchaseOrderStatusParams) error
	ApproveOrder(ctx context.Context, q db.DBTX, p db.ApprovePurchaseOrderParams) error
	AddReceived(ctx context.Context, q db.DBTX, p db.AddReceivedQuantityParams) error

	// goods receipts
	CreateGR(ctx context.Context, q db.DBTX, p db.CreateGoodsReceiptParams) (db.CreateGoodsReceiptRow, error)
	CreateGRItem(ctx context.Context, q db.DBTX, p db.CreateGoodsReceiptItemParams) (int64, error)
	GRByID(ctx context.Context, id int64) (db.GetGoodsReceiptByIDRow, error)
	GRItems(ctx context.Context, grID int64) ([]db.ListGoodsReceiptItemsRow, error)
	ListGRs(ctx context.Context, f GRFilter) ([]db.ListGoodsReceiptsRow, int, error)
	ReceiveStock(ctx context.Context, q db.DBTX, p db.RawStockAddParams, grID, itemID int64, userID *int64) error

	// debts
	ListDebts(ctx context.Context, f DebtFilter) ([]db.ListDebtsRow, int, DebtSummary, error)

	// vouchers
	CreateVoucher(ctx context.Context, q db.DBTX, p db.CreateVoucherParams) (int64, error)
	VoucherByID(ctx context.Context, id int64) (db.GetVoucherByIDRow, error)
	VoucherByIDForUpdate(ctx context.Context, q db.DBTX, id int64) (db.GetVoucherByIDForUpdateRow, error)
	UpdateVoucherStatus(ctx context.Context, q db.DBTX, p db.UpdateVoucherStatusParams) error
	UpdateVoucherStatusAny(ctx context.Context, q db.DBTX, p db.UpdateVoucherStatusAnyParams) error
	UpdateVoucher(ctx context.Context, q db.DBTX, p db.UpdateVoucherParams) error
	ListVouchers(ctx context.Context, f VoucherFilter) ([]db.ListVouchersRow, int, error)
	MarkVoucherPrinted(ctx context.Context, q db.DBTX, p db.MarkVoucherPrintedParams) error
	PurchaseOrderSupplierID(ctx context.Context, q db.DBTX, poID int64) (int64, error)

	// lookups
	SupplierExists(ctx context.Context, id int64) (bool, error)
	WarehouseExists(ctx context.Context, id int64) (bool, error)
	ProductExists(ctx context.Context, id int64) (bool, error)
}
