package sqlite

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

	"switchboard-core/store"
)

// DB is the shared database connection pool.
var DB *sql.DB

// timeFmt is the SQLite-compatible time format for explicit timestamps.
// Must match datetime('now') output used in schema DEFAULT expressions.
const timeFmt = "2006-01-02 15:04:05"

// ── Time Scanner ────────────────────────────
// modernc/sqlite returns TEXT columns as raw strings, not time.Time.
// st() wraps a *time.Time destination so Scan auto-parses the string.

type sqliteTime struct{ t *time.Time }

func st(t *time.Time) *sqliteTime { return &sqliteTime{t: t} }

func (s *sqliteTime) Scan(src interface{}) error {
	if src == nil {
		return nil
	}
	switch v := src.(type) {
	case time.Time:
		*s.t = v
		return nil
	case int64:
		// modernc/sqlite stores Go time.Time as Unix timestamp (int64).
		// This happens when code passes *time.Time directly as a ?
		// parameter instead of using datetime('now') in SQL.
		*s.t = time.Unix(v, 0).UTC()
		return nil
	case string:
		for _, f := range timeFormats {
			if p, err := time.Parse(f, v); err == nil {
				*s.t = p
				return nil
			}
		}
		return fmt.Errorf("sqliteTime: cannot parse %q", v)
	default:
		return fmt.Errorf("sqliteTime: unsupported type %T", src)
	}
}

var timeFormats = []string{
	"2006-01-02 15:04:05",
	"2006-01-02T15:04:05Z",
	time.RFC3339,
	"2006-01-02 15:04:05.000000000+00:00",
	"2006-01-02 15:04:05Z07:00",                // modernc: time.Time UTC → "2025-03-11 16:00:00Z"
	"2006-01-02 15:04:05.999999999Z07:00",       // modernc: time.Time with fractional seconds
}

// stN wraps a *time.Time pointer for nullable columns.
type sqliteTimeNullable struct{ t **time.Time }

func stN(t **time.Time) *sqliteTimeNullable { return &sqliteTimeNullable{t: t} }

func (s *sqliteTimeNullable) Scan(src interface{}) error {
	if src == nil {
		*s.t = nil
		return nil
	}
	var parsed time.Time
	scanner := st(&parsed)
	if err := scanner.Scan(src); err != nil {
		return err
	}
	*s.t = &parsed
	return nil
}

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

// ── Dynamic SQL Builder (? placeholders) ────

// UpdateBuilder constructs a dynamic UPDATE with ? placeholders.
type UpdateBuilder struct {
	table string
	sets  []string
	args  []interface{}
	where []string
}

func NewUpdate(table string) *UpdateBuilder {
	return &UpdateBuilder{table: table}
}

func (b *UpdateBuilder) Set(col string, val interface{}) *UpdateBuilder {
	b.sets = append(b.sets, col+" = ?")
	b.args = append(b.args, val)
	return b
}

func (b *UpdateBuilder) SetNull(col string) *UpdateBuilder {
	b.sets = append(b.sets, col+" = NULL")
	return b
}

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))
}

func (b *UpdateBuilder) SetIf(col string, val interface{}, set bool) *UpdateBuilder {
	if set {
		return b.Set(col, val)
	}
	return b
}

// SetExpr sets a column to a raw SQL expression (not parameterized).
func (b *UpdateBuilder) SetExpr(col, expr string) *UpdateBuilder {
	b.sets = append(b.sets, col+" = "+expr)
	return b
}

// nowExpr returns the SQL expression for "current timestamp" in ISO 8601 format.
const nowExpr = "datetime('now')"

func (b *UpdateBuilder) Where(col string, val interface{}) *UpdateBuilder {
	b.where = append(b.where, col+" = ?")
	b.args = append(b.args, val)
	return b
}

func (b *UpdateBuilder) HasSets() bool {
	return len(b.sets) > 0
}

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

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

// ── Select Builder ──────────────────────────

type SelectBuilder struct {
	cols    string
	table   string
	joins   []string
	where   []string
	args    []interface{}
	orderBy string
	limit   int
	offset  int
}

func NewSelect(cols, table string) *SelectBuilder {
	return &SelectBuilder{cols: cols, table: table}
}

func (b *SelectBuilder) Join(join string) *SelectBuilder {
	b.joins = append(b.joins, join)
	return b
}

func (b *SelectBuilder) Where(clause string, args ...interface{}) *SelectBuilder {
	b.where = append(b.where, clause)
	b.args = append(b.args, args...)
	return b
}

func (b *SelectBuilder) WhereRaw(clause string) *SelectBuilder {
	b.where = append(b.where, clause)
	return b
}

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

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
}

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
}

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
}

// ── JSON Helpers ────────────────────────────

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

// ScanJSON scans a TEXT column (JSON) 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 JSON type: %T", src)
	}
	return json.Unmarshal(data, dst)
}

// ArrayToJSON converts a []string to JSON text for storage.
func ArrayToJSON(arr []string) string {
	if arr == nil {
		return "[]"
	}
	b, _ := json.Marshal(arr)
	return string(b)
}

// ScanArray scans a JSON text array column into []string.
func ScanArray(src interface{}) []string {
	if src == nil {
		return nil
	}
	var data string
	switch v := src.(type) {
	case string:
		data = v
	case []byte:
		data = string(v)
	default:
		return nil
	}
	var arr []string
	if err := json.Unmarshal([]byte(data), &arr); err != nil {
		return nil
	}
	return arr
}

// ── Nullable Helpers ────────────────────────

func NullableString(ns sql.NullString) string {
	if ns.Valid {
		return ns.String
	}
	return ""
}

func NullableStringPtr(ns sql.NullString) *string {
	if ns.Valid {
		return &ns.String
	}
	return nil
}

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

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

// ── UUID Generation ─────────────────────────

// NewID generates a new UUID string. Imported from the uuid package
// at the store level to keep the helper package dependency-light.
// Callers should use store.NewID() instead.
