package database

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
)

// ── SQL Dialect Adapter ─────────────────────
//
// Q adapts Postgres-flavoured SQL to the current dialect.
// On Postgres it is a no-op. On SQLite it rewrites:
//   - $1, $2 … $20  →  ?
//   - ::jsonb, ::text  →  removed
//   - NOW()  →  datetime('now')
//   - true / false  →  1 / 0  (bare keywords only, not inside strings)
//   - NULLS LAST  →  removed (SQLite default)
//   - COALESCE(x::text, …)  →  COALESCE(x, …)
//
// Call it at every database.DB.Query / Exec / QueryRow site in handlers.
func Q(query string) string {
	if !IsSQLite() {
		return query
	}
	q := query

	// Replace $N placeholders high-to-low to avoid $1 matching inside $10.
	for i := 20; i >= 1; i-- {
		q = strings.ReplaceAll(q, fmt.Sprintf("$%d", i), "?")
	}

	// Postgres type casts
	q = strings.ReplaceAll(q, "::jsonb", "")
	q = strings.ReplaceAll(q, "::text", "")

	// Time functions
	q = strings.ReplaceAll(q, "NOW()", "datetime('now')")

	// Boolean literals (bare keywords, not inside quotes)
	q = strings.ReplaceAll(q, "= true", "= 1")
	q = strings.ReplaceAll(q, "= false", "= 0")
	q = strings.ReplaceAll(q, "COALESCE(is_private, false)", "COALESCE(is_private, 0)")

	// NULL sort order (SQLite puts NULLs last by default for DESC)
	q = strings.ReplaceAll(q, "NULLS LAST", "")

	// Case-insensitive LIKE (SQLite LIKE is already case-insensitive for ASCII)
	q = strings.ReplaceAll(q, "ILIKE", "LIKE")

	return q
}

// QArgs adapts a Postgres query AND its args for the current dialect.
//
// Unlike Q() which only rewrites the query string, QArgs also expands the
// args slice to match positional ? placeholders. In Postgres, $1 can appear
// multiple times and references the same arg. In SQLite, each ? is
// positional — if $1 appears twice, the arg must appear twice.
//
// Use QArgs for any handler query that references $N more than once
// (typically subqueries like "WHERE user_id = $1 OR ... participant_id = $1").
//
// On Postgres, returns query and args unchanged.
func QArgs(query string, args ...interface{}) (string, []interface{}) {
	if !IsSQLite() {
		return query, args
	}

	// First, apply the non-placeholder rewrites
	q := query
	q = strings.ReplaceAll(q, "::jsonb", "")
	q = strings.ReplaceAll(q, "::text", "")
	q = strings.ReplaceAll(q, "NOW()", "datetime('now')")
	q = strings.ReplaceAll(q, "= true", "= 1")
	q = strings.ReplaceAll(q, "= false", "= 0")
	q = strings.ReplaceAll(q, "COALESCE(is_private, false)", "COALESCE(is_private, 0)")
	q = strings.ReplaceAll(q, "NULLS LAST", "")
	q = strings.ReplaceAll(q, "ILIKE", "LIKE")

	// Walk the query left-to-right, find each $N, record which arg index
	// it maps to, replace with ?, and build a new args list in order.
	var newArgs []interface{}
	var result strings.Builder
	i := 0
	for i < len(q) {
		if q[i] == '$' && i+1 < len(q) && q[i+1] >= '1' && q[i+1] <= '9' {
			// Parse the number after $
			j := i + 1
			for j < len(q) && q[j] >= '0' && q[j] <= '9' {
				j++
			}
			numStr := q[i+1 : j]
			n := 0
			for _, c := range numStr {
				n = n*10 + int(c-'0')
			}
			// $N is 1-based, args is 0-based
			if n >= 1 && n <= len(args) {
				newArgs = append(newArgs, args[n-1])
			}
			result.WriteByte('?')
			i = j
		} else {
			result.WriteByte(q[i])
			i++
		}
	}

	return result.String(), newArgs
}

// ── Safe Query Wrappers ─────────────────────
//
// These wrappers replace the pattern:
//   database.DB.QueryRow(database.Q(sql), args...)
// with:
//   database.QueryRow(sql, args...)
//
// They call QArgs internally, handling $N→? conversion AND arg expansion
// for repeated placeholder references. Use these for ALL handler-level
// SQL that uses $N placeholders.

// QueryRow executes a query with dialect adaptation and arg expansion.
func QueryRow(query string, args ...interface{}) *sql.Row {
	q, expanded := QArgs(query, args...)
	return DB.QueryRow(q, expanded...)
}

// QueryRowContext is the context-aware variant of QueryRow.
func QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
	q, expanded := QArgs(query, args...)
	return DB.QueryRowContext(ctx, q, expanded...)
}

// Query executes a query returning rows, with dialect adaptation.
func Query(query string, args ...interface{}) (*sql.Rows, error) {
	q, expanded := QArgs(query, args...)
	return DB.Query(q, expanded...)
}

// QueryContext is the context-aware variant of Query.
func QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
	q, expanded := QArgs(query, args...)
	return DB.QueryContext(ctx, q, expanded...)
}

// Exec executes a statement with dialect adaptation.
func Exec(query string, args ...interface{}) (sql.Result, error) {
	q, expanded := QArgs(query, args...)
	return DB.Exec(q, expanded...)
}

// ExecContext is the context-aware variant of Exec.
func ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
	q, expanded := QArgs(query, args...)
	return DB.ExecContext(ctx, q, expanded...)
}

// InsertReturningID executes an INSERT … RETURNING id query.
// On Postgres it uses RETURNING directly.
// On SQLite it strips RETURNING, generates a UUID, prepends it to the args,
// adds an "id" column to the INSERT, and executes.
func InsertReturningID(query string, args ...interface{}) (string, error) {
	if !IsSQLite() {
		var id string
		err := DB.QueryRow(query, args...).Scan(&id)
		return id, err
	}

	// Generate ID for SQLite
	id := uuid.New().String()

	// Adapt SQL: strip RETURNING, convert placeholders, add id column+value
	q := Q(query)

	// Remove RETURNING clause
	if idx := strings.Index(strings.ToUpper(q), "RETURNING"); idx >= 0 {
		q = strings.TrimSpace(q[:idx])
	}

	// Inject id column and value into INSERT
	// Pattern: INSERT INTO table (col1, col2, ...) VALUES (?, ?, ...)
	q = injectIDColumn(q)

	// Prepend id to args
	newArgs := make([]interface{}, 0, len(args)+1)
	newArgs = append(newArgs, id)
	newArgs = append(newArgs, args...)

	_, err := DB.Exec(q, newArgs...)
	return id, err
}

// InjectIDForTest adds "id" as the first column and "?" as the first value
// in an INSERT statement. Exported for use in test helpers.
func InjectIDForTest(q string) string {
	return injectIDColumn(q)
}

// injectIDColumn adds "id" as the first column and "?" as the first value
// in an INSERT statement.
func injectIDColumn(q string) string {
	// Find the opening paren of the column list
	upper := strings.ToUpper(q)
	insertIdx := strings.Index(upper, "INSERT INTO")
	if insertIdx < 0 {
		return q
	}

	// Find first ( after INSERT INTO tablename
	firstParen := strings.Index(q[insertIdx:], "(")
	if firstParen < 0 {
		return q
	}
	firstParen += insertIdx

	// Find VALUES (
	valuesIdx := strings.Index(upper, "VALUES")
	if valuesIdx < 0 {
		return q
	}
	valuesParen := strings.Index(q[valuesIdx:], "(")
	if valuesParen < 0 {
		return q
	}
	valuesParen += valuesIdx

	// Insert "id, " after first ( and "?, " after VALUES (
	result := q[:firstParen+1] + "id, " + q[firstParen+1:valuesParen+1] + "?, " + q[valuesParen+1:]
	return result
}

// ── Time Scan Helpers ───────────────────────
//
// SQLite returns timestamps as TEXT strings. These helpers wrap *time.Time
// and sql.NullTime destinations so Scan() works on both dialects.

// ScanTime wraps a *time.Time for use in handler-level Scan() calls.
// On Postgres, time.Time scans natively. On SQLite, parses from string.
type ScanTime struct{ T *time.Time }

func (s *ScanTime) Scan(src interface{}) error {
	if src == nil {
		return nil
	}
	switch v := src.(type) {
	case time.Time:
		*s.T = v
		return nil
	case string:
		for _, f := range compatTimeFormats {
			if p, err := time.Parse(f, v); err == nil {
				*s.T = p
				return nil
			}
		}
		return fmt.Errorf("ScanTime: cannot parse %q", v)
	default:
		return fmt.Errorf("ScanTime: unsupported type %T", src)
	}
}

// ST creates a ScanTime wrapper for use in Scan() argument lists.
func ST(t *time.Time) *ScanTime { return &ScanTime{T: t} }

// ScanNullTime wraps a nullable time destination.
type ScanNullTime struct{ T **time.Time }

func (s *ScanNullTime) 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
}

// SNT creates a ScanNullTime wrapper for use in Scan() argument lists.
func SNT(t **time.Time) *ScanNullTime { return &ScanNullTime{T: t} }

// NullTimeValue extracts a time.Time from sql.NullTime or *time.Time,
// returning the zero value if nil/invalid. Useful in JSON serialization.
func NullTimeValue(t *time.Time) time.Time {
	if t == nil {
		return time.Time{}
	}
	return *t
}

var compatTimeFormats = []string{
	"2006-01-02 15:04:05",
	"2006-01-02T15:04:05Z",
	time.RFC3339,
	"2006-01-02 15:04:05.000000000+00:00",
}

// ── Unique Constraint Detection ─────────────

// IsUniqueViolation checks if an error is a unique constraint violation,
// working on both Postgres and SQLite.
func IsUniqueViolation(err error) bool {
	if err == nil {
		return false
	}
	msg := err.Error()
	return strings.Contains(msg, "duplicate key") || // Postgres
		strings.Contains(msg, "UNIQUE constraint failed") // SQLite
}
