package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"time"

	"github.com/robfig/cron/v3"

	"github.com/prism/proxy/internal/api"
	"github.com/prism/proxy/internal/cache"
	"github.com/prism/proxy/internal/configbuilder"
	"github.com/prism/proxy/internal/core"
	"github.com/prism/proxy/internal/geodata"
	"github.com/prism/proxy/internal/health"
	applog "github.com/prism/proxy/internal/log"
	"github.com/prism/proxy/internal/metrics"
	"github.com/prism/proxy/internal/mihomoapi"
	"github.com/prism/proxy/internal/service"
	"github.com/prism/proxy/internal/store"
	"github.com/prism/proxy/internal/subscription"
	"github.com/prism/proxy/internal/trafficlog"
)

func main() {
	dataDir := flag.String("data", "data", "data directory")
	flag.Parse()

	log.Printf("Prism starting, data dir: %s", *dataDir)

	if err := os.MkdirAll(*dataDir, 0o755); err != nil {
		log.Fatal(err)
	}

	st, err := store.Open(*dataDir)
	if err != nil {
		log.Fatal(err)
	}
	defer st.Close()
	log.Printf("database ready")

	if n, err := st.BackfillNodeCountries(context.Background()); err != nil {
		log.Printf("country backfill: %v", err)
	} else if n > 0 {
		log.Printf("country tags backfilled for %d nodes", n)
	}

	core.InitDataDir(*dataDir)

	c := cache.New()
	engine := core.NewEngine()
	builder := configbuilder.New(st, *dataDir)
	logCollector := applog.NewCollector(st, 5000, true)
	subSvc := subscription.NewService(st)

	app := &service.App{
		Store:        st,
		Cache:        c,
		Engine:       engine,
		Builder:      builder,
		Subscription: subSvc,
		Logs:         logCollector,
		DataDir:      *dataDir,
	}

	ctx := context.Background()
	apiBase, secret, _ := app.MihomoAPIBase(ctx)
	hc := health.NewChecker(st, c, apiBase, secret)
	mc := metrics.NewCollector(st, c, apiBase, secret)
	trafficCollector := trafficlog.NewCollector(st, 5000)
	trafficCollector.Start(ctx, mihomoapi.New(apiBase, secret))

	settings, _ := st.GetSettings(ctx)
	applyGeoSettingsOnStart(*dataDir, settings)
	if settings["geo_auto_download"] != "false" {
		go func() {
			log.Printf("checking geo data in background...")
			cfg := geodata.ConfigFromSettings(settings)
			if updated, err := geodata.Ensure(*dataDir, cfg, false); err != nil {
				log.Printf("geo data: %v", err)
				logCollector.Add("warn", "prism", "geo data: "+err.Error())
			} else if len(updated) > 0 {
				log.Printf("geo data downloaded: %v", updated)
			}
		}()
	}

	logCollector.Add("info", "prism", "Prism started")
	log.Printf("loading proxy engine...")

	if err := app.ReloadEngine(ctx); err != nil {
		log.Printf("initial engine reload: %v (will retry after config)", err)
	} else {
		log.Printf("proxy engine ready")
	}
	go func() {
		warmCtx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
		defer cancel()
		_, _ = mc.Refresh(warmCtx)
		app.RefreshEngineStatusAsync()
	}()

	srv := api.NewServer(app, hc, mc, subSvc, logCollector, trafficCollector)
	router := srv.Router()

	apiPort := settings["api_port"]
	if apiPort == "" {
		apiPort = "8080"
	}

	httpServer := &http.Server{
		Addr:    ":" + apiPort,
		Handler: router,
	}

	cr := cron.New()
	_, _ = cr.AddFunc("@every 1m", func() {
	_, _ = mc.Refresh(context.Background())
	})
	_, _ = cr.AddFunc("@every 5m", func() {
		settings, _ := st.GetSettings(context.Background())
		sec, _ := strconv.Atoi(settings["health_interval_sec"])
		if sec <= 0 {
			sec = 300
		}
		// health checker runs on its own schedule approximation
		_ = hc.TestAll(context.Background(), nil)
	})
	_, _ = cr.AddFunc("@every 10m", func() {
		syncSubscriptions(app, subSvc)
	})
	cr.Start()

	go func() {
		log.Printf("Prism API listening on :%s", apiPort)
		if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatal(err)
		}
	}()

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	<-sig

	shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	_ = httpServer.Shutdown(shutdownCtx)
	cr.Stop()
	engine.Shutdown()
}

func syncSubscriptions(app *service.App, subSvc *subscription.Service) {
	ctx := context.Background()
	settings, err := app.Store.GetSettings(ctx)
	if err != nil || settings["sub_sync_enabled"] != "true" {
		return
	}
	subs, err := app.Store.ListSubscriptions(ctx)
	if err != nil {
		return
	}
	now := time.Now()
	for _, sub := range subs {
		if !sub.Enabled {
			continue
		}
		if sub.LastFetchAt != nil && now.Sub(*sub.LastFetchAt) < time.Duration(sub.IntervalSec)*time.Second {
			continue
		}
		if err := subSvc.FetchAndStore(ctx, sub.ID); err != nil {
			app.Logs.Add("error", "subscription", fmt.Sprintf("sync %s failed: %v", sub.Name, err))
			continue
		}
	}
	_ = app.ReloadEngine(ctx)
}
