package partners

import (
	"context"
	"errors"

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

// Repository implements Store on a pgx pool (sqlc-backed).
type Repository struct {
	q    *db.Queries
	pool *pgxpool.Pool
}

func NewRepository(pool *pgxpool.Pool) *Repository {
	return &Repository{q: db.New(pool), pool: pool}
}

// Pool exposes the underlying pgx pool for the small number of ad-hoc
// statements that carry a cross-package invariant (customer password reset
// resolves customers.user_id exactly as the deactivation hook does).
func (r *Repository) Pool() *pgxpool.Pool { return r.pool }

// CustomerLoginUser resolves the B2B login behind a customer record
// (000151). sqlc models the LEFT-joined user as nullable; the service decides
// what a missing login means.
func (r *Repository) CustomerLoginUser(ctx context.Context, customerID int64) (db.GetCustomerLoginUserRow, error) {
	return r.q.GetCustomerLoginUser(ctx, customerID)
}

// SetUserPassword overwrites the linked login's bcrypt hash (000151). The
// service treats execrows==0 as the not-found case.
func (r *Repository) SetUserPassword(ctx context.Context, userID int64, passwordHash string) error {
	n, err := r.q.SetUserPassword(ctx, db.SetUserPasswordParams{ID: userID, Password: passwordHash})
	if err != nil {
		return err
	}
	if n == 0 {
		return ErrNotFound
	}
	return nil
}

// --- suppliers ---------------------------------------------------------------

func (r *Repository) ListSuppliers(ctx context.Context, f ListFilter) ([]db.ListSuppliersRow, int, error) {
	var s pgtype.Text
	if f.Search != "" {
		s = pgtype.Text{String: f.Search, Valid: true}
	}
	active := activeStatus(f.Status)
	total, err := r.q.CountSuppliers(ctx, db.CountSuppliersParams{Search: s, IsActive: active})
	if err != nil {
		return nil, 0, err
	}
	rows, err := r.q.ListSuppliers(ctx, db.ListSuppliersParams{
		Limit: int32(f.PerPage), Offset: int32((f.Page - 1) * f.PerPage), Search: s, IsActive: active,
		SortBy: textOrNil(f.SortBy), SortDir: textOrNil(f.SortDir),
	})
	if err != nil {
		return nil, 0, err
	}
	return rows, int(total), nil
}

func (r *Repository) SupplierByID(ctx context.Context, id int64) (db.GetSupplierByIDRow, error) {
	row, err := r.q.GetSupplierByID(ctx, id)
	if err != nil {
		return db.GetSupplierByIDRow{}, mapErr(err)
	}
	return row, nil
}

func (r *Repository) SupplierByCode(ctx context.Context, code string) (db.GetSupplierByCodeRow, error) {
	row, err := r.q.GetSupplierByCode(ctx, code)
	if err != nil {
		return db.GetSupplierByCodeRow{}, mapErr(err)
	}
	return row, nil
}

func (r *Repository) CreateSupplier(ctx context.Context, p db.CreateSupplierParams) (int64, error) {
	id, err := r.q.CreateSupplier(ctx, p)
	if err != nil {
		return 0, mapWriteErr(err)
	}
	return id, nil
}

func (r *Repository) UpdateSupplier(ctx context.Context, p db.UpdateSupplierParams) error {
	n, err := r.q.UpdateSupplier(ctx, p)
	if err != nil {
		return mapWriteErr(err)
	}
	if n == 0 {
		return ErrNotFound
	}
	return nil
}

func (r *Repository) DeleteSupplier(ctx context.Context, id int64) error {
	n, err := r.q.SoftDeleteSupplier(ctx, id)
	if err != nil {
		return err
	}
	if n == 0 {
		return ErrNotFound
	}
	return nil
}

// --- customers ---------------------------------------------------------------

func (r *Repository) ListCustomers(ctx context.Context, f ListFilter) ([]db.ListCustomersRow, int, error) {
	var s pgtype.Text
	if f.Search != "" {
		s = pgtype.Text{String: f.Search, Valid: true}
	}
	var ct pgtype.Text
	if f.CustomerType != "" {
		ct = pgtype.Text{String: f.CustomerType, Valid: true}
	}
	active := activeStatus(f.Status)
	total, err := r.q.CountCustomers(ctx, db.CountCustomersParams{Search: s, IsActive: active, CustomerType: ct})
	if err != nil {
		return nil, 0, err
	}
	rows, err := r.q.ListCustomers(ctx, db.ListCustomersParams{
		Limit: int32(f.PerPage), Offset: int32((f.Page - 1) * f.PerPage), Search: s, IsActive: active,
		SortBy: textOrNil(f.SortBy), SortDir: textOrNil(f.SortDir),
		CustomerType: ct,
	})
	if err != nil {
		return nil, 0, err
	}
	return rows, int(total), nil
}

func activeStatus(status string) pgtype.Bool {
	if status == "active" {
		return pgtype.Bool{Bool: true, Valid: true}
	}
	if status == "inactive" {
		return pgtype.Bool{Bool: false, Valid: true}
	}
	return pgtype.Bool{}
}

func (r *Repository) CustomerByID(ctx context.Context, id int64) (db.GetCustomerByIDRow, error) {
	row, err := r.q.GetCustomerByID(ctx, id)
	if err != nil {
		return db.GetCustomerByIDRow{}, mapErr(err)
	}
	return row, nil
}

func (r *Repository) CustomerByCode(ctx context.Context, code string) (db.GetCustomerByCodeRow, error) {
	row, err := r.q.GetCustomerByCode(ctx, code)
	if err != nil {
		return db.GetCustomerByCodeRow{}, mapErr(err)
	}
	return row, nil
}

func (r *Repository) CreateCustomer(ctx context.Context, p db.CreateCustomerParams) (int64, error) {
	id, err := r.q.CreateCustomer(ctx, p)
	if err != nil {
		return 0, mapWriteErr(err)
	}
	return id, nil
}

func (r *Repository) UpdateCustomer(ctx context.Context, p db.UpdateCustomerParams) error {
	n, err := r.q.UpdateCustomer(ctx, p)
	if err != nil {
		return mapWriteErr(err)
	}
	if n == 0 {
		return ErrNotFound
	}
	return nil
}

func (r *Repository) DeleteCustomer(ctx context.Context, id int64) error {
	n, err := r.q.SoftDeleteCustomer(ctx, id)
	if err != nil {
		return err
	}
	if n == 0 {
		return ErrNotFound
	}
	return nil
}

// --- helpers -----------------------------------------------------------------

func mapErr(err error) error {
	if errors.Is(err, pgx.ErrNoRows) {
		return ErrNotFound
	}
	return err
}

func mapWriteErr(err error) error {
	var pge *pgconn.PgError
	if errors.As(err, &pge) && pge.Code == "23505" {
		return ErrDuplicateCode
	}
	return err
}

// CustomerIsEcommerceAdminByUserID reports whether the user's linked customer
// carries the storefront backoffice flag (E-Commerce application admin).
// RBAC audit 2026-08-25.
func (r *Repository) CustomerIsEcommerceAdminByUserID(ctx context.Context, userID int64) (bool, error) {
	return r.q.CustomerIsEcomAdminByUserID(ctx, pgtype.Int8{Int64: userID, Valid: true})
}

func (r *Repository) CustomerProfileForUser(ctx context.Context, userID int64) (db.CustomerProfileForUserRow, error) {
	// Was int32(userID). users.id is bigint, so that silently truncated every
	// id above 2^31, and the neighbouring CustomerIsEcomAdminByUserID already
	// passed the value as pgtype.Int8. sqlc now types this parameter from the
	// column, which is what surfaced the truncation.
	return r.q.CustomerProfileForUser(ctx, pgtype.Int8{Int64: userID, Valid: true})
}

func (r *Repository) UpdateCustomerProfileForUser(ctx context.Context, p db.UpdateCustomerProfileForUserParams) (int64, error) {
	return r.q.UpdateCustomerProfileForUser(ctx, p)
}

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