// Package partners implements suppliers and customers master data.
// Supplier/customer codes are global atomic sequences: SUP-<3>/CUS-<3>
// counting incl. trashed (business-rules.md §1). Both soft-delete.
package partners

import (
	"context"
	"time"

	"github.com/TaruLab/keluargamart-backend/internal/db"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/shopspring/decimal"
)

// SupplierView is the API response shape for a supplier. Persistence uses
// db rows; nullable columns are mapped to "" to keep the JSON contract.
type SupplierView struct {
	ID            int64           `json:"id"`
	Code          string          `json:"code"`
	Name          string          `json:"name"`
	ContactPerson string          `json:"contact_person"`
	Phone         string          `json:"phone"`
	Email         string          `json:"email"`
	Address       string          `json:"address"`
	City          string          `json:"city"`
	TaxNumber     string          `json:"tax_number"`
	BankName      string          `json:"bank_name"`
	BankAccount   string          `json:"bank_account"`
	CreditLimit   decimal.Decimal `json:"credit_limit"`
	PaymentTerms  int             `json:"payment_terms"`
	IsActive      bool            `json:"is_active"`
	DeletedAt     *time.Time      `json:"deleted_at"`
}

// CustomerView is the API response shape for a customer.
type CustomerView struct {
	ID              int64           `json:"id"`
	Code            string          `json:"code"`
	Name            string          `json:"name"`
	ContactPerson   string          `json:"contact_person"`
	Phone           string          `json:"phone"`
	Email           string          `json:"email"`
	Address         string          `json:"address"`
	City            string          `json:"city"`
	Kecamatan       string          `json:"kecamatan"`
	Kelurahan       string          `json:"kelurahan"`
	Desa            string          `json:"desa"`
	TaxNumber       string          `json:"tax_number"`
	CreditLimit     decimal.Decimal `json:"credit_limit"`
	PaymentTerms    int             `json:"payment_terms"`
	CustomerType    string          `json:"customer_type"`
	AssignedSalesID *int64          `json:"assigned_sales_id"`
	IsActive        bool            `json:"is_active"`
	DeletedAt       *time.Time      `json:"deleted_at"`
}

type SupplierInput struct {
	Name          string          `json:"name"`
	ContactPerson string          `json:"contact_person"`
	Phone         string          `json:"phone"`
	Email         string          `json:"email"`
	Address       string          `json:"address"`
	City          string          `json:"city"`
	TaxNumber     string          `json:"tax_number"`
	BankName      string          `json:"bank_name"`
	BankAccount   string          `json:"bank_account"`
	CreditLimit   decimal.Decimal `json:"credit_limit"`
	PaymentTerms  int             `json:"payment_terms"`
	IsActive      bool            `json:"is_active"`
}

// ProfileView is the storefront's own-company payload. Separate from
// CustomerView on purpose: the directory view carries the credit limit, the
// assigned salesperson and the active flag, which are internal decisions a
// buyer must see about themselves no more than about anyone else.
type ProfileView struct {
	ID                int64       `json:"id"`
	Code              string      `json:"code"`
	Name              string      `json:"name"`
	ContactPerson     pgtype.Text `json:"contact_person"`
	Phone             pgtype.Text `json:"phone"`
	Email             pgtype.Text `json:"email"`
	Address           pgtype.Text `json:"address"`
	City              pgtype.Text `json:"city"`
	Kecamatan         pgtype.Text `json:"kecamatan"`
	Kelurahan         pgtype.Text `json:"kelurahan"`
	Desa              pgtype.Text `json:"desa"`
	TaxNumber         pgtype.Text `json:"tax_number"`
	Website           pgtype.Text `json:"website"`
	BillingAddressID  pgtype.Int8 `json:"billing_address_id"`
	ShippingAddressID pgtype.Int8 `json:"shipping_address_id"`
}

// ProfileInput is a PARTIAL update of the caller's own company record, and
// every field is a pointer for one reason: "not sent" and "sent empty" must
// stay distinguishable.
//
// CustomerInput cannot do this. Its fields are plain strings, so an omitted
// field arrives as "" — and the UPDATE's COALESCE then sees an empty string,
// not NULL, and picks it. The result is that submitting a one-field form
// silently blanks every other field. That is not hypothetical: it is exactly
// what PUT /customers/me did when it first took CustomerInput, returning
// city="" and kecamatan="" after a payload that mentioned neither. The same
// class of bug hit the settings price lists in 8d570e1.
//
// So: nil means leave the column alone, and a pointer to "" means clear it. A
// company profile legitimately needs both — drop a website, remove a default
// address — and one type has to carry that distinction.
//
// It is separate from CustomerInput rather than a widening of it because the
// admin create/update path takes plain strings on purpose, where "absent" and
// "blank" are the same request.
type ProfileInput struct {
	Name          *string `json:"name"`
	ContactPerson *string `json:"contact_person"`
	Phone         *string `json:"phone"`
	Email         *string `json:"email"`
	Address       *string `json:"address"`
	City          *string `json:"city"`
	Kecamatan     *string `json:"kecamatan"`
	Kelurahan     *string `json:"kelurahan"`
	Desa          *string `json:"desa"`
	TaxNumber     *string `json:"tax_number"`
	Website       *string `json:"website"`

	// NullInt, not *int64: a plain pointer cannot tell "leave it alone" from
	// "clear it to NULL", so a client could set a default address and never
	// remove it. Text fields above do not need this — an empty string is a
	// legitimate company-profile value, so "clear" and "set to empty" are the
	// same act and "" says it. An address pointer has no such natural empty
	// state, which is exactly why it needs the extra state. See NullInt.
	BillingAddressID  NullInt `json:"billing_address_id"`
	ShippingAddressID NullInt `json:"shipping_address_id"`
}

type CustomerInput struct {
	Name            string          `json:"name"`
	ContactPerson   string          `json:"contact_person"`
	Phone           string          `json:"phone"`
	Email           string          `json:"email"`
	Address         string          `json:"address"`
	City            string          `json:"city"`
	Kecamatan       string          `json:"kecamatan"`
	Kelurahan       string          `json:"kelurahan"`
	Desa            string          `json:"desa"`
	TaxNumber       string          `json:"tax_number"`
	CreditLimit     decimal.Decimal `json:"credit_limit"`
	PaymentTerms    int             `json:"payment_terms"`
	CustomerType    string          `json:"customer_type"`
	AssignedSalesID *int64          `json:"assigned_sales_id"`
	IsActive        bool            `json:"is_active"`
}

type ListFilter struct {
	Page         int
	PerPage      int
	Search       string
	Status       string
	CustomerType string
	SortBy       string
	SortDir      string
}

// Store is the partners persistence seam (sqlc-backed in production, fake in tests).
type Store interface {
	// suppliers
	ListSuppliers(ctx context.Context, f ListFilter) ([]db.ListSuppliersRow, int, error)
	SupplierByID(ctx context.Context, id int64) (db.GetSupplierByIDRow, error)
	SupplierByCode(ctx context.Context, code string) (db.GetSupplierByCodeRow, error)
	CreateSupplier(ctx context.Context, p db.CreateSupplierParams) (int64, error)
	UpdateSupplier(ctx context.Context, p db.UpdateSupplierParams) error
	DeleteSupplier(ctx context.Context, id int64) error
	// customers
	ListCustomers(ctx context.Context, f ListFilter) ([]db.ListCustomersRow, int, error)
	CustomerByID(ctx context.Context, id int64) (db.GetCustomerByIDRow, error)
	CustomerByCode(ctx context.Context, code string) (db.GetCustomerByCodeRow, error)
	CreateCustomer(ctx context.Context, p db.CreateCustomerParams) (int64, error)
	UpdateCustomer(ctx context.Context, p db.UpdateCustomerParams) error
	DeleteCustomer(ctx context.Context, id int64) error
	// Admin password reset (000151).
	CustomerLoginUser(ctx context.Context, customerID int64) (db.GetCustomerLoginUserRow, error)
	SetUserPassword(ctx context.Context, userID int64, passwordHash string) error
	// The caller's OWN customer record, for the storefront account profile.
	// Keyed by users.id because the buyer must never name an id to read a
	// company record — the directory routes stay behind the PII gate.
	CustomerProfileForUser(ctx context.Context, userID int64) (db.CustomerProfileForUserRow, error)
	UpdateCustomerProfileForUser(ctx context.Context, p db.UpdateCustomerProfileForUserParams) (int64, error)
}
