package main

import (
	"flag"
	"log"
	"log/slog"
	"sort"
	"time"

	"git.mchus.pro/mchus/quoteforge/internal/appstate"
	"git.mchus.pro/mchus/quoteforge/internal/localdb"
	"git.mchus.pro/mchus/quoteforge/internal/models"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

type projectTimestampRow struct {
	UUID      string
	UpdatedAt time.Time
}

type updatePlanRow struct {
	UUID            string
	Code            string
	Variant         string
	LocalUpdatedAt  time.Time
	ServerUpdatedAt time.Time
}

func main() {
	defaultLocalDBPath, err := appstate.ResolveDBPath("")
	if err != nil {
		log.Fatalf("failed to resolve default local SQLite path: %v", err)
	}

	localDBPath := flag.String("localdb", defaultLocalDBPath, "path to local SQLite database (default: user state dir or QFS_DB_PATH)")
	apply := flag.Bool("apply", false, "apply updates to local SQLite (default is preview only)")
	flag.Parse()

	local, err := localdb.New(*localDBPath)
	if err != nil {
		log.Fatalf("failed to initialize local database: %v", err)
	}
	defer local.Close()

	if !local.HasSettings() {
		log.Fatalf("SQLite connection settings are not configured. Run qfs setup first.")
	}

	dsn, err := local.GetDSN()
	if err != nil {
		log.Fatalf("failed to build DSN from SQLite settings: %v", err)
	}

	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		log.Fatalf("failed to connect to MariaDB: %v", err)
	}

	serverRows, err := loadServerProjects(db)
	if err != nil {
		log.Fatalf("failed to load server projects: %v", err)
	}

	localProjects, err := local.GetAllProjects(true)
	if err != nil {
		log.Fatalf("failed to load local projects: %v", err)
	}

	plan := buildUpdatePlan(localProjects, serverRows)
	printPlan(plan, *apply)

	if !*apply || len(plan) == 0 {
		return
	}

	updated := 0
	for i := range plan {
		project, err := local.GetProjectByUUID(plan[i].UUID)
		if err != nil {
			log.Printf("skip %s: load local project: %v", plan[i].UUID, err)
			continue
		}
		project.UpdatedAt = plan[i].ServerUpdatedAt
		if err := local.SaveProjectPreservingUpdatedAt(project); err != nil {
			log.Printf("skip %s: save local project: %v", plan[i].UUID, err)
			continue
		}
		updated++
	}

	log.Printf("updated %d local project timestamps", updated)
}

func loadServerProjects(db *gorm.DB) (map[string]time.Time, error) {
	var rows []projectTimestampRow
	if err := db.Model(&models.Project{}).
		Select("uuid, updated_at").
		Find(&rows).Error; err != nil {
		return nil, err
	}

	out := make(map[string]time.Time, len(rows))
	for _, row := range rows {
		if row.UUID == "" {
			continue
		}
		out[row.UUID] = row.UpdatedAt
	}
	return out, nil
}

func buildUpdatePlan(localProjects []localdb.LocalProject, serverRows map[string]time.Time) []updatePlanRow {
	plan := make([]updatePlanRow, 0)
	for i := range localProjects {
		project := localProjects[i]
		serverUpdatedAt, ok := serverRows[project.UUID]
		if !ok {
			continue
		}
		if project.UpdatedAt.Equal(serverUpdatedAt) {
			continue
		}
		plan = append(plan, updatePlanRow{
			UUID:            project.UUID,
			Code:            project.Code,
			Variant:         project.Variant,
			LocalUpdatedAt:  project.UpdatedAt,
			ServerUpdatedAt: serverUpdatedAt,
		})
	}

	sort.Slice(plan, func(i, j int) bool {
		if plan[i].Code != plan[j].Code {
			return plan[i].Code < plan[j].Code
		}
		return plan[i].Variant < plan[j].Variant
	})

	return plan
}

func printPlan(plan []updatePlanRow, apply bool) {
	mode := "preview"
	if apply {
		mode = "apply"
	}
	log.Printf("project updated_at resync mode=%s changes=%d", mode, len(plan))
	if len(plan) == 0 {
		log.Printf("no local project timestamps need resync")
		return
	}
	for _, row := range plan {
		variant := row.Variant
		if variant == "" {
			variant = "main"
		}
		log.Printf("%s [%s] local=%s server=%s", row.Code, variant, formatStamp(row.LocalUpdatedAt), formatStamp(row.ServerUpdatedAt))
	}
	if !apply {
		slog.Info("Re-run with -apply to write server updated_at into local SQLite.")
	}
}

func formatStamp(value time.Time) string {
	if value.IsZero() {
		return "zero"
	}
	return value.Format(time.RFC3339)
}
