package handlers

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/lib/pq"

	"switchboard-core/database"
)

// ── SafeJSON ────────────────────────────────
// SafeJSON pre-marshals the response body, guaranteeing that a
// serialisation failure produces a clean 500 instead of a truncated
// 200 with half-written JSON.  Use for any endpoint whose response
// contains json.RawMessage or other pass-through data from the DB.

func SafeJSON(c *gin.Context, code int, obj interface{}) {
	body, err := json.Marshal(obj)
	if err != nil {
		log.Printf("⚠ SafeJSON: marshal failed: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "response serialization failed",
		})
		return
	}
	c.Data(code, "application/json; charset=utf-8", body)
}

// ── JSON column scanner ─────────────────────
// Handles both Postgres ([]byte) and SQLite (string) return types.
// Validates content before accepting it — corrupt data is replaced
// with "{}" and a warning is logged so admins can find the bad row.

type jsonScanner struct{ dest *json.RawMessage }

func scanJSON(dest *json.RawMessage) *jsonScanner { return &jsonScanner{dest: dest} }

func (s *jsonScanner) Scan(src interface{}) error {
	switch v := src.(type) {
	case []byte:
		if len(v) == 0 || !json.Valid(v) {
			log.Printf("⚠ scanJSON: invalid JSON in column (len=%d, preview=%.60q), defaulting to {}", len(v), v)
			*s.dest = json.RawMessage("{}")
			return nil
		}
		// CRITICAL: copy the driver buffer. json.RawMessage(v) aliases the
		// underlying []byte owned by database/sql. The driver may reuse that
		// memory on the next rows.Scan() call, corrupting our data after the
		// fact. This caused intermittent SafeJSON 500s on paginated list
		// endpoints where row N's Settings pointed to row N+1's overwritten
		// buffer. Fixed in v0.21.7.
		cp := make([]byte, len(v))
		copy(cp, v)
		*s.dest = json.RawMessage(cp)
	case string:
		b := []byte(v)
		if len(b) == 0 || !json.Valid(b) {
			log.Printf("⚠ scanJSON: invalid JSON in column (len=%d, preview=%.60q), defaulting to {}", len(b), b)
			*s.dest = json.RawMessage("{}")
			return nil
		}
		*s.dest = json.RawMessage(b)
	case nil:
		*s.dest = json.RawMessage("{}")
	default:
		*s.dest = json.RawMessage("{}")
	}
	return nil
}

// ── Tags column scanner ─────────────────────
// SQLite stores tags as a JSON text array; Postgres uses text[].
// Both paths validate and fall back to an empty slice on error.

type tagsScanner struct {
	dest *[]string
}

func scanTags(dest *[]string) *tagsScanner {
	return &tagsScanner{dest: dest}
}

func (s *tagsScanner) Scan(src interface{}) error {
	if src == nil {
		*s.dest = []string{}
		return nil
	}

	if database.IsSQLite() {
		var raw string
		switch v := src.(type) {
		case string:
			raw = v
		case []byte:
			raw = string(v)
		default:
			*s.dest = []string{}
			return nil
		}
		if raw == "" {
			*s.dest = []string{}
			return nil
		}
		var arr []string
		if err := json.Unmarshal([]byte(raw), &arr); err != nil {
			log.Printf("⚠ scanTags: invalid JSON array in column (preview=%.60q), defaulting to []", raw)
			*s.dest = []string{}
			return nil
		}
		*s.dest = arr
		return nil
	}

	// Postgres: delegate to pq, but catch errors
	if err := pq.Array(s.dest).Scan(src); err != nil {
		log.Printf("⚠ scanTags: pq.Array scan failed (%v), defaulting to []", err)
		*s.dest = []string{}
		return nil
	}
	return nil
}
