package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/go-sql-driver/mysql"

	"lingniu/vehicle-data-platform/apps/api/internal/businessscope"
)

func main() {
	if err := run(); err != nil {
		log.Printf("OneOS scope sync failed: %v", err)
		os.Exit(1)
	}
}

func run() error {
	sourceDSN, err := normalizedDSN("ONEOS_MYSQL_DSN", os.Getenv("ONEOS_MYSQL_DSN"), true)
	if err != nil {
		return err
	}
	targetDSN, err := normalizedDSN("MYSQL_DSN", os.Getenv("MYSQL_DSN"), false)
	if err != nil {
		return err
	}
	timeout := time.Duration(envInt("ONEOS_SCOPE_SYNC_TIMEOUT_SEC", 60)) * time.Second
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	source, err := openDB(ctx, sourceDSN, 2)
	if err != nil {
		return fmt.Errorf("open OneOS read-only database: %w", err)
	}
	defer source.Close()
	target, err := openDB(ctx, targetDSN, 4)
	if err != nil {
		return fmt.Errorf("open vehicle platform database: %w", err)
	}
	defer target.Close()
	candidates, err := businessscope.ReadCandidates(ctx, source)
	if err != nil {
		return err
	}
	snapshot, err := businessscope.BuildSnapshot(candidates, time.Now())
	if err != nil {
		return err
	}
	maxRejected := envInt("ONEOS_SCOPE_MAX_REJECTED", 100)
	maxRejectedRatio, err := envFloat("ONEOS_SCOPE_MAX_REJECT_RATIO", 0.10)
	if err != nil {
		return err
	}
	if err := businessscope.ValidateSnapshot(snapshot, maxRejected, maxRejectedRatio); err != nil {
		return err
	}
	result, err := businessscope.Publish(ctx, target, snapshot)
	if err != nil {
		return err
	}
	log.Printf("OneOS scope sync complete changed=%t candidates=%d accepted=%d rejected=%d version=%s",
		result.Changed, snapshot.Candidates, len(snapshot.Items), len(snapshot.Rejections), snapshot.SourceVersion)
	return nil
}

func normalizedDSN(name, raw string, requireOneOSDatabase bool) (string, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return "", fmt.Errorf("%s is required", name)
	}
	config, err := mysql.ParseDSN(raw)
	if err != nil {
		return "", fmt.Errorf("parse %s: %w", name, err)
	}
	if requireOneOSDatabase && config.DBName != "ln_asset_management" {
		return "", fmt.Errorf("%s must select ln_asset_management", name)
	}
	location, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		return "", fmt.Errorf("load Asia/Shanghai timezone: %w", err)
	}
	config.ParseTime = true
	config.Loc = location
	if config.Timeout <= 0 {
		config.Timeout = 10 * time.Second
	}
	if config.ReadTimeout <= 0 {
		config.ReadTimeout = 30 * time.Second
	}
	if config.WriteTimeout <= 0 {
		config.WriteTimeout = 10 * time.Second
	}
	return config.FormatDSN(), nil
}

func openDB(ctx context.Context, dsn string, maxConnections int) (*sql.DB, error) {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, err
	}
	db.SetMaxOpenConns(maxConnections)
	db.SetMaxIdleConns(1)
	db.SetConnMaxLifetime(5 * time.Minute)
	if err := db.PingContext(ctx); err != nil {
		_ = db.Close()
		return nil, err
	}
	return db, nil
}

func envInt(key string, fallback int) int {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}
	parsed, err := strconv.Atoi(value)
	if err != nil {
		return fallback
	}
	return parsed
}

func envFloat(key string, fallback float64) (float64, error) {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback, nil
	}
	parsed, err := strconv.ParseFloat(value, 64)
	if err != nil || parsed < 0 || parsed > 1 {
		return 0, fmt.Errorf("%s must be a number between 0 and 1", key)
	}
	return parsed, nil
}
