package handlers

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"

	"switchboard-core/database"
)

const testJWTSecret = "test-secret-key-for-handler-tests"

type testHarness struct {
	router *gin.Engine
	t      *testing.T
}

func (h *testHarness) request(method, path, token string, body interface{}) *httptest.ResponseRecorder {
	var bodyReader *bytes.Reader
	if body != nil {
		b, _ := json.Marshal(body)
		bodyReader = bytes.NewReader(b)
	} else {
		bodyReader = bytes.NewReader(nil)
	}
	req := httptest.NewRequest(method, path, bodyReader)
	req.Header.Set("Content-Type", "application/json")
	if token != "" {
		req.Header.Set("Authorization", "Bearer "+token)
	}
	w := httptest.NewRecorder()
	h.router.ServeHTTP(w, req)
	return w
}

func makeToken(userID, email, _ string) string {
	claims := Claims{
		UserID: userID,
		Email:  email,
		RegisteredClaims: jwt.RegisteredClaims{
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
			Issuer:    "switchboard-core",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	s, _ := token.SignedString([]byte(testJWTSecret))
	return s
}

// seedInsertReturningID inserts a row, pre-generating an id for SQLite.
// Expects query like: INSERT INTO users (col1, col2, ...) VALUES ($1, $2, ...) RETURNING id
func seedInsertReturningID(t *testing.T, query string, args ...interface{}) string {
	t.Helper()
	id := uuid.New().String()

	// Inject id column and value into the query
	// "INSERT INTO users (username, ..." → "INSERT INTO users (id, username, ..."
	q := strings.Replace(query, "(username,", "(id, username,", 1)
	// Strip RETURNING clause
	if idx := strings.Index(q, " RETURNING "); idx > 0 {
		q = q[:idx]
	}

	if database.IsSQLite() {
		// Convert $N → ?
		for i := len(args) + 1; i >= 1; i-- {
			q = strings.ReplaceAll(q, "$"+itoa(i), "?")
		}
		// Add ? for the injected id column
		q = strings.Replace(q, "VALUES (", "VALUES (?, ", 1)
		newArgs := append([]interface{}{id}, args...)
		_, err := database.TestDB.Exec(q, newArgs...)
		if err != nil {
			t.Fatalf("seedInsertReturningID (sqlite): %v\nquery: %s", err, q)
		}
		return id
	}

	// Postgres: renumber $N → $N+1, insert $1 for id
	for i := len(args); i >= 1; i-- {
		q = strings.ReplaceAll(q, "$"+itoa(i), "$"+itoa(i+1))
	}
	q = strings.Replace(q, "(id,", "(id,", 1)
	// Add id as first value placeholder
	q = strings.Replace(q, "VALUES (", "VALUES ($1, ", 1)
	newArgs := append([]interface{}{id}, args...)
	_, err := database.TestDB.Exec(q, newArgs...)
	if err != nil {
		t.Fatalf("seedInsertReturningID (pg): %v\nquery: %s", err, q)
	}
	return id
}

func itoa(i int) string {
	if i < 10 {
		return string(rune('0' + i))
	}
	return string(rune('0'+i/10)) + string(rune('0'+i%10))
}

func decode(args ...interface{}) {
	switch len(args) {
	case 2:
		w := args[0].(*httptest.ResponseRecorder)
		json.NewDecoder(w.Body).Decode(args[1])
	case 3:
		t := args[0].(*testing.T)
		t.Helper()
		w := args[1].(*httptest.ResponseRecorder)
		if err := json.NewDecoder(w.Body).Decode(args[2]); err != nil {
			t.Fatalf("decode response: %v", err)
		}
	}
}

// dialectSQL returns query as-is. Placeholder conversion happens in seedInsertReturningID.
func dialectSQL(query string) string {
	if database.IsSQLite() {
		q := query
		for i := 20; i >= 1; i-- {
			q = strings.ReplaceAll(q, "$"+itoa(i), "?")
		}
		return q
	}
	return query
}

func init() {
	_ = http.StatusOK
}
