package database

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"gorm.io/gorm"
)

// Seed files run on first boot. Order matters: lookups first, then rows
// that depend on them (admin user is independent; task templates reference
// lookup categories).
var initialSeedFiles = []string{
	"001_lookups.sql",
	"003_admin_user.sql",
	"003_task_templates.sql",
}

// SeedInitialDataApplied is set true during startup if the seed migration
// just ran. main.go reads it post-cache-init to invalidate stale Redis
// entries for /api/static_data (24h TTL) so clients see the new lookups.
var SeedInitialDataApplied bool

func init() {
	RegisterDataMigration("20260414_seed_initial_data", seedInitialData)
}

// seedInitialData executes the baseline SQL seed files exactly once. Because
// each INSERT uses ON CONFLICT DO UPDATE, rerunning the files is safe if the
// tracking row is ever lost.
func seedInitialData(tx *gorm.DB) error {
	sqlDB, err := tx.DB()
	if err != nil {
		return fmt.Errorf("get underlying sql.DB: %w", err)
	}

	for _, filename := range initialSeedFiles {
		content, err := readSeedFile(filename)
		if err != nil {
			return fmt.Errorf("read seed %s: %w", filename, err)
		}

		for i, stmt := range splitSQL(content) {
			if _, err := sqlDB.Exec(stmt); err != nil {
				preview := stmt
				if len(preview) > 120 {
					preview = preview[:120] + "..."
				}
				return fmt.Errorf("seed %s statement %d failed: %w\nstatement: %s", filename, i+1, err, preview)
			}
		}
	}
	SeedInitialDataApplied = true
	return nil
}

func readSeedFile(filename string) (string, error) {
	paths := []string{
		filepath.Join("seeds", filename),
		filepath.Join("./seeds", filename),
		filepath.Join("/app/seeds", filename),
	}
	var lastErr error
	for _, p := range paths {
		content, err := os.ReadFile(p)
		if err == nil {
			return string(content), nil
		}
		lastErr = err
	}
	return "", lastErr
}

// splitSQL splits raw SQL into individual statements, respecting single-quoted
// string literals (including '' escapes) and skipping comment-only fragments.
func splitSQL(sqlContent string) []string {
	var out []string
	var current strings.Builder
	inString := false
	stringChar := byte(0)

	for i := 0; i < len(sqlContent); i++ {
		c := sqlContent[i]

		if (c == '\'' || c == '"') && (i == 0 || sqlContent[i-1] != '\\') {
			if !inString {
				inString = true
				stringChar = c
			} else if c == stringChar {
				if c == '\'' && i+1 < len(sqlContent) && sqlContent[i+1] == '\'' {
					current.WriteByte(c)
					i++
					current.WriteByte(sqlContent[i])
					continue
				}
				inString = false
			}
		}

		if c == ';' && !inString {
			current.WriteByte(c)
			stmt := strings.TrimSpace(current.String())
			if stmt != "" && !isSQLCommentOnly(stmt) {
				out = append(out, stmt)
			}
			current.Reset()
			continue
		}

		current.WriteByte(c)
	}

	if stmt := strings.TrimSpace(current.String()); stmt != "" && !isSQLCommentOnly(stmt) {
		out = append(out, stmt)
	}
	return out
}

func isSQLCommentOnly(stmt string) bool {
	for _, line := range strings.Split(stmt, "\n") {
		line = strings.TrimSpace(line)
		if line != "" && !strings.HasPrefix(line, "--") {
			return false
		}
	}
	return true
}
