package postgres

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"strings"

	"switchboard-core/store"
)

// DB is the shared database connection pool.
// Set during initialization via SetDB.
var DB *sql.DB

// SetDB configures the shared database connection for all stores.
func SetDB(db *sql.DB) {
	DB = db
}

// ── Dynamic SQL Builder ─────────────────────
// Replaces the copy-pasted addClause/addField pattern.

// UpdateBuilder constructs a dynamic UPDATE statement.
type UpdateBuilder struct {
	table   string
	sets    []string
	args    []interface{}
	where   []string
	argIdx  int
}

// NewUpdate creates an UpdateBuilder for the given table.
func NewUpdate(table string) *UpdateBuilder {
	return &UpdateBuilder{table: table}
}

// Set adds a column=value pair to the UPDATE.
func (b *UpdateBuilder) Set(col string, val interface{}) *UpdateBuilder {
	b.argIdx++
	b.sets = append(b.sets, fmt.Sprintf("%s = $%d", col, b.argIdx))
	b.args = append(b.args, val)
	return b
}

// SetNull adds a col = NULL pair to the UPDATE.
func (b *UpdateBuilder) SetNull(col string) *UpdateBuilder {
	b.sets = append(b.sets, fmt.Sprintf("%s = NULL", col))
	return b
}

// SetJSON adds a JSONB column from a map.
func (b *UpdateBuilder) SetJSON(col string, val interface{}) *UpdateBuilder {
	data, err := json.Marshal(val)
	if err != nil {
		data = []byte("{}")
	}
	return b.Set(col, string(data))
}

// SetIf conditionally adds a column if the pointer is non-nil.
func (b *UpdateBuilder) SetIf(col string, val interface{}, set bool) *UpdateBuilder {
	if set {
		return b.Set(col, val)
	}
	return b
}

// Where adds a WHERE condition.
func (b *UpdateBuilder) Where(col string, val interface{}) *UpdateBuilder {
	b.argIdx++
	b.where = append(b.where, fmt.Sprintf("%s = $%d", col, b.argIdx))
	b.args = append(b.args, val)
	return b
}

// HasSets returns true if any SET clauses were added.
func (b *UpdateBuilder) HasSets() bool {
	return len(b.sets) > 0
}

// Build returns the SQL string and args.
func (b *UpdateBuilder) Build() (string, []interface{}) {
	sql := fmt.Sprintf("UPDATE %s SET %s", b.table, strings.Join(b.sets, ", "))
	if len(b.where) > 0 {
		sql += " WHERE " + strings.Join(b.where, " AND ")
	}
	return sql, b.args
}

// Exec executes the built UPDATE.
func (b *UpdateBuilder) Exec(db *sql.DB) (sql.Result, error) {
	q, args := b.Build()
	return db.Exec(q, args...)
}

// ── Query Builder ───────────────────────────

// SelectBuilder constructs a dynamic SELECT statement.
type SelectBuilder struct {
	cols    string
	table   string
	joins   []string
	where   []string
	args    []interface{}
	orderBy string
	limit   int
	offset  int
	argIdx  int
}

// NewSelect creates a SelectBuilder.
func NewSelect(cols, table string) *SelectBuilder {
	return &SelectBuilder{cols: cols, table: table}
}

// Join adds a JOIN clause.
func (b *SelectBuilder) Join(join string) *SelectBuilder {
	b.joins = append(b.joins, join)
	return b
}

// Where adds a WHERE condition with a parameter.
func (b *SelectBuilder) Where(clause string, args ...interface{}) *SelectBuilder {
	for _, arg := range args {
		b.argIdx++
		clause = strings.Replace(clause, "?", fmt.Sprintf("$%d", b.argIdx), 1)
		b.args = append(b.args, arg)
	}
	b.where = append(b.where, clause)
	return b
}

// WhereRaw adds a WHERE condition without parameters.
func (b *SelectBuilder) WhereRaw(clause string) *SelectBuilder {
	b.where = append(b.where, clause)
	return b
}

// OrderBy sets the ORDER BY clause.
func (b *SelectBuilder) OrderBy(col, order string) *SelectBuilder {
	if order == "" {
		order = "DESC"
	}
	b.orderBy = fmt.Sprintf("%s %s", col, strings.ToUpper(order))
	return b
}

// Paginate sets LIMIT and OFFSET from ListOptions.
func (b *SelectBuilder) Paginate(opts store.ListOptions) *SelectBuilder {
	if opts.Limit > 0 {
		b.limit = opts.Limit
	}
	if opts.Offset > 0 {
		b.offset = opts.Offset
	}
	if opts.Sort != "" {
		b.OrderBy(opts.Sort, opts.Order)
	}
	return b
}

// Build returns the SQL string and args.
func (b *SelectBuilder) Build() (string, []interface{}) {
	q := fmt.Sprintf("SELECT %s FROM %s", b.cols, b.table)
	for _, j := range b.joins {
		q += " " + j
	}
	if len(b.where) > 0 {
		q += " WHERE " + strings.Join(b.where, " AND ")
	}
	if b.orderBy != "" {
		q += " ORDER BY " + b.orderBy
	}
	if b.limit > 0 {
		q += fmt.Sprintf(" LIMIT %d", b.limit)
	}
	if b.offset > 0 {
		q += fmt.Sprintf(" OFFSET %d", b.offset)
	}
	return q, b.args
}

// CountBuild returns a SELECT COUNT(*) version of the query (no order/limit).
func (b *SelectBuilder) CountBuild() (string, []interface{}) {
	q := fmt.Sprintf("SELECT COUNT(*) FROM %s", b.table)
	for _, j := range b.joins {
		q += " " + j
	}
	if len(b.where) > 0 {
		q += " WHERE " + strings.Join(b.where, " AND ")
	}
	return q, b.args
}

// ── JSONB Helpers ───────────────────────────

// ToJSON marshals a value to JSON bytes for JSONB columns.
func ToJSON(v interface{}) []byte {
	if v == nil {
		return []byte("{}")
	}
	b, err := json.Marshal(v)
	if err != nil {
		return []byte("{}")
	}
	return b
}

// ScanJSON scans a JSONB column into a target.
func ScanJSON(src interface{}, dst interface{}) error {
	if src == nil {
		return nil
	}
	var data []byte
	switch v := src.(type) {
	case []byte:
		data = v
	case string:
		data = []byte(v)
	default:
		return fmt.Errorf("unsupported JSONB type: %T", src)
	}
	return json.Unmarshal(data, dst)
}

// NullableString returns the string value or empty string from sql.NullString.
func NullableString(ns sql.NullString) string {
	if ns.Valid {
		return ns.String
	}
	return ""
}

// NullableStringPtr returns a *string from sql.NullString (nil if not valid).
func NullableStringPtr(ns sql.NullString) *string {
	if ns.Valid {
		return &ns.String
	}
	return nil
}

// NullableFloat64Ptr returns a *float64 from sql.NullFloat64.
func NullableFloat64Ptr(nf sql.NullFloat64) *float64 {
	if nf.Valid {
		return &nf.Float64
	}
	return nil
}

// NullableIntPtr returns an *int from sql.NullInt64.
func NullableIntPtr(ni sql.NullInt64) *int {
	if ni.Valid {
		v := int(ni.Int64)
		return &v
	}
	return nil
}
