package purchasing

import (
	"time"

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

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

func orderFromRow(r db.GetPurchaseOrderByIDRow) *PurchaseOrder {
	return &PurchaseOrder{
		ID: r.ID, PoNumber: r.PoNumber, SupplierID: r.SupplierID, WarehouseID: r.WarehouseID,
		CreatedBy: int8ToPtr(r.CreatedBy), OrderDate: r.OrderDate, ExpectedDate: nonEmptyPtr(r.ExpectedDate), DueDate: nonEmptyPtr(r.DueDate),
		Status: r.Status, Subtotal: r.Subtotal, TaxAmount: r.TaxAmount, DiscountAmount: r.DiscountAmount,
		TotalAmount: r.TotalAmount, PaidAmount: r.PaidAmount, RemainingAmount: r.RemainingAmount,
		Notes: textToPtr(r.Notes), CreatedAt: r.CreatedAt.Time, UpdatedAt: r.UpdatedAt.Time,
		SupplierName:    orEmptyPgText(r.SupplierName),
		WarehouseName:   orEmptyPgText(r.WarehouseName),
		SupplierAddress: textToPtr(r.SupplierAddress),
		SupplierPhone:   textToPtr(r.SupplierPhone),
		SupplierEmail:   textToPtr(r.SupplierEmail),
		CreatedByName:   orEmptyPgText(r.CreatedByName),
		ApprovedByName:  orEmptyPgText(r.ApprovedByName),
		ApprovedAt:      tsPtrOrNil(r.ApprovedAt),
		PrintCount:      int(r.PrintCount),
		PrintedAt:       tsPtrOrNil(r.PrintedAt),
		PrintedByName:   orEmptyPgText(r.PrintedByName),
	}
}

func orderFromLockedRow(r db.GetPurchaseOrderByIDForUpdateRow) *PurchaseOrder {
	return &PurchaseOrder{
		ID: r.ID, PoNumber: r.PoNumber, SupplierID: r.SupplierID, WarehouseID: r.WarehouseID,
		CreatedBy: int8ToPtr(r.CreatedBy), OrderDate: r.OrderDate, ExpectedDate: nonEmptyPtr(r.ExpectedDate), DueDate: nonEmptyPtr(r.DueDate),
		Status: r.Status, Subtotal: r.Subtotal, TaxAmount: r.TaxAmount, DiscountAmount: r.DiscountAmount,
		TotalAmount: r.TotalAmount, PaidAmount: r.PaidAmount, RemainingAmount: r.RemainingAmount,
		Notes: textToPtr(r.Notes), CreatedAt: r.CreatedAt.Time, UpdatedAt: r.UpdatedAt.Time,
	}
}

func orderFromListRow(r db.ListPurchaseOrdersRow) PurchaseOrder {
	o := PurchaseOrder{
		ID: r.ID, PoNumber: r.PoNumber, SupplierID: r.SupplierID, WarehouseID: r.WarehouseID,
		CreatedBy: int8ToPtr(r.CreatedBy), OrderDate: r.OrderDate, ExpectedDate: nonEmptyPtr(r.ExpectedDate), DueDate: nonEmptyPtr(r.DueDate),
		Status: r.Status, Subtotal: r.Subtotal, TaxAmount: r.TaxAmount, DiscountAmount: r.DiscountAmount,
		TotalAmount: r.TotalAmount, PaidAmount: r.PaidAmount, RemainingAmount: r.RemainingAmount,
		Notes: textToPtr(r.Notes), CreatedAt: r.CreatedAt.Time, UpdatedAt: r.UpdatedAt.Time,
		SupplierName: r.SupplierName, WarehouseName: r.WarehouseName,
		PrintCount: int(r.PrintCount),
	}
	if r.PrintedAt != "" {
		// List query casts printed_at to a UTC RFC3339 string (to_char) so the
		// ERP gets a parseable value; Get returns the raw timestamptz.
		if t, perr := time.Parse("2006-01-02T15:04:05Z", r.PrintedAt); perr == nil {
			o.PrintedAt = &t
		}
	}
	return o
}

func itemFromRow(r db.ListPurchaseOrderItemsRow) POItem {
	return POItem{ID: r.ID, PurchaseOrderID: r.PurchaseOrderID, ProductID: r.ProductID, Quantity: r.Quantity,
		ReceivedQuantity: r.ReceivedQuantity, UnitPrice: r.UnitPrice, Discount: r.Discount, Subtotal: r.Subtotal,
		ProductCode: r.Code, ProductName: r.Name, UnitSymbol: orEmptyPgText(r.UnitSymbol)}
}
func itemFromLockedRow(r db.ListPurchaseOrderItemsForUpdateRow) POItem {
	return POItem{ID: r.ID, PurchaseOrderID: r.PurchaseOrderID, ProductID: r.ProductID, Quantity: r.Quantity,
		ReceivedQuantity: r.ReceivedQuantity, UnitPrice: r.UnitPrice, Discount: r.Discount, Subtotal: r.Subtotal,
		ProductCode: r.Code, ProductName: r.Name}
}

func grFromRow(r db.GetGoodsReceiptByIDRow) *GoodsReceipt {
	return &GoodsReceipt{ID: r.ID, GrNumber: r.GrNumber, PurchaseOrderID: r.PurchaseOrderID, WarehouseID: r.WarehouseID,
		ReceivedBy: int8ToPtr(r.ReceivedBy), ReceivedDate: r.ReceivedDate, Notes: textToPtr(r.Notes), Status: r.Status, CreatedAt: r.CreatedAt.Time}
}

func grFromListRow(r db.ListGoodsReceiptsRow) GoodsReceipt {
	return GoodsReceipt{ID: r.ID, GrNumber: r.GrNumber, PurchaseOrderID: r.PurchaseOrderID, WarehouseID: r.WarehouseID,
		ReceivedBy: int8ToPtr(r.ReceivedBy), ReceivedDate: r.ReceivedDate, Notes: textToPtr(r.Notes), Status: r.Status,
		CreatedAt: r.CreatedAt.Time, PONumber: r.PoNumber, SupplierName: r.SupplierName, WarehouseName: r.WarehouseName}
}

func grItemFromRow(r db.ListGoodsReceiptItemsRow) GRItem {
	return GRItem{ID: r.ID, GoodsReceiptID: r.GoodsReceiptID, PurchaseOrderItemID: int8Value(r.PurchaseOrderItemID),
		ProductID: r.ProductID, QuantityOrdered: r.QuantityOrdered, QuantityReceived: r.QuantityReceived,
		BatchNumber: textToPtr(r.BatchNumber), ExpiredDate: nonEmptyPtr(r.ExpiredDate), ProductCode: r.Code, ProductName: r.Name}
}

func debtFromRow(r db.ListDebtsRow) DebtRow {
	// RejectedValue datang sebagai interface{} dari subquery — normalisasi ke
	// decimal supaya drill-down punya angka, bukan null diam-diam.
	rejected := decimal.Zero
	switch v := r.RejectedValue.(type) {
	case decimal.Decimal:
		rejected = v
	case string:
		rejected, _ = decimal.NewFromString(v)
	case []byte:
		rejected, _ = decimal.NewFromString(string(v))
	case pgtype.Numeric:
		if fv, err := v.Float64Value(); err == nil && fv.Valid {
			rejected = decimal.NewFromFloat(fv.Float64)
		}
	}
	return DebtRow{POID: r.ID, PoNumber: r.PoNumber, SupplierID: r.SupplierID, SupplierName: r.Name,
		OrderDate: r.OrderDate, DueDate: nonEmptyPtr(r.DueDate), Status: r.Status,
		TotalAmount: r.TotalAmount, PaidAmount: r.PaidAmount, Remaining: r.RemainingAmount,
		RejectedValue: rejected, RejectedLines: r.RejectedLines}
}

func voucherFromRow(r db.GetVoucherByIDRow) *Voucher {
	return &Voucher{ID: r.ID, VoucherNumber: r.VoucherNumber, Type: r.Type, Description: textToPtr(r.Description),
		VoucherDate: r.VoucherDate, Amount: r.Amount, Status: r.Status, CreatedBy: int8ToPtr(r.CreatedBy),
		SupplierID: int8ToPtr(r.SupplierID), POID: int8ToPtr(r.PoID), CashAccountCode: orEmptyPgText(r.CashAccountCode),
		PrintedAt: tsPtrOrNil(r.PrintedAt), PrintedBy: int8ToPtr(r.PrintedBy)}
}

func voucherFromListRow(r db.ListVouchersRow) Voucher {
	return *voucherFromRow(db.GetVoucherByIDRow(r))
}

func createOrderParams(o *PurchaseOrder) db.CreatePurchaseOrderParams {
	return db.CreatePurchaseOrderParams{PoNumber: o.PoNumber, SupplierID: o.SupplierID, WarehouseID: o.WarehouseID,
		CreatedBy: int8OrNil(o.CreatedBy), OrderDate: dateOrNil(o.OrderDate), ExpectedDate: datePtrOrNil(o.ExpectedDate), DueDate: datePtrOrNil(o.DueDate),
		Status: o.Status, Subtotal: o.Subtotal, TaxAmount: o.TaxAmount, DiscountAmount: o.DiscountAmount,
		TotalAmount: o.TotalAmount, PaidAmount: o.PaidAmount, RemainingAmount: o.RemainingAmount, Notes: textPtrOrNil(o.Notes)}
}

func updateOrderParams(o *PurchaseOrder) db.UpdatePurchaseOrderParams {
	return db.UpdatePurchaseOrderParams{ID: o.ID, PoNumber: o.PoNumber, SupplierID: o.SupplierID, WarehouseID: o.WarehouseID,
		OrderDate: dateOrNil(o.OrderDate), ExpectedDate: datePtrOrNil(o.ExpectedDate), DueDate: datePtrOrNil(o.DueDate),
		Status: o.Status, Subtotal: o.Subtotal, TaxAmount: o.TaxAmount, DiscountAmount: o.DiscountAmount,
		TotalAmount: o.TotalAmount, PaidAmount: o.PaidAmount, RemainingAmount: o.RemainingAmount, Notes: textPtrOrNil(o.Notes)}
}

func createItemParams(it *POItem) db.CreatePurchaseOrderItemParams {
	return db.CreatePurchaseOrderItemParams{PurchaseOrderID: it.PurchaseOrderID, ProductID: it.ProductID,
		Quantity: it.Quantity, UnitPrice: it.UnitPrice, Discount: it.Discount, Subtotal: it.Subtotal}
}

func createGRParams(g *GoodsReceipt) db.CreateGoodsReceiptParams {
	return db.CreateGoodsReceiptParams{GrNumber: g.GrNumber, PurchaseOrderID: g.PurchaseOrderID, WarehouseID: g.WarehouseID,
		ReceivedBy: int8OrNil(g.ReceivedBy), ReceivedDate: dateOrNil(g.ReceivedDate), Notes: textPtrOrNil(g.Notes), Status: g.Status}
}

func createGRItemParams(it *GRItem) db.CreateGoodsReceiptItemParams {
	return db.CreateGoodsReceiptItemParams{GoodsReceiptID: it.GoodsReceiptID, PurchaseOrderItemID: int8(it.PurchaseOrderItemID),
		ProductID: it.ProductID, QuantityOrdered: it.QuantityOrdered, QuantityReceived: it.QuantityReceived,
		QuantityRejected: it.QuantityRejected, BatchNumber: textPtrOrNil(it.BatchNumber), ExpiredDate: datePtrOrNil(it.ExpiredDate),
		DefectNotes: textPtrOrNil(it.DefectNotes)}
}

func createVoucherParams(v *Voucher) db.CreateVoucherParams {
	return db.CreateVoucherParams{VoucherNumber: v.VoucherNumber, Type: v.Type, Description: textPtrOrNil(v.Description),
		VoucherDate: dateOrNil(v.VoucherDate), Amount: v.Amount, Status: v.Status, CreatedBy: int8OrNil(v.CreatedBy),
		SupplierID: int8OrNil(v.SupplierID), PoID: int8OrNil(v.POID), CashAccountCode: textOrNil(v.CashAccountCode)}
}

func updateVoucherParams(v *Voucher) db.UpdateVoucherParams {
	return db.UpdateVoucherParams{ID: v.ID, Type: v.Type, Description: textPtrOrNil(v.Description),
		VoucherDate: dateOrNil(v.VoucherDate), Amount: v.Amount, Status: v.Status}
}

func orEmptyPgText(v pgtype.Text) string { return v.String }

func orEmptyString(v string) string { return v }

func tsPtrOrNil(v pgtype.Timestamptz) *time.Time {
	if v.Valid {
		t := v.Time
		return &t
	}
	return nil
}

func int8ToPtr(v pgtype.Int8) *int64 {
	if !v.Valid {
		return nil
	}
	x := v.Int64
	return &x
}

func int8OrNil(v *int64) pgtype.Int8 {
	if v == nil {
		return pgtype.Int8{}
	}
	return int8(*v)
}

func int8(v int64) pgtype.Int8 { return pgtype.Int8{Int64: v, Valid: true} }
func int8Value(v pgtype.Int8) int64 {
	if !v.Valid {
		return 0
	}
	return v.Int64
}

func textToPtr(v pgtype.Text) *string {
	if !v.Valid {
		return nil
	}
	s := v.String
	return &s
}

func textPtrOrNil(v *string) pgtype.Text {
	if v == nil {
		return pgtype.Text{}
	}
	return pgtype.Text{String: *v, Valid: true}
}

func textOrNil(v string) pgtype.Text {
	if v == "" {
		return pgtype.Text{}
	}
	return pgtype.Text{String: v, Valid: true}
}

func dateOrNil(v string) pgtype.Date {
	if t, err := time.Parse("2006-01-02", v); err == nil {
		return pgtype.Date{Time: t, Valid: true}
	}
	return pgtype.Date{}
}

func datePtrOrNil(v *string) pgtype.Date {
	if v == nil {
		return pgtype.Date{}
	}
	return dateOrNil(*v)
}

func nonEmptyPtr(v string) *string {
	if v == "" {
		return nil
	}
	return &v
}
