package main

import (
	"context"
	"errors"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/api"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/auth"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/billing"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/config"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/db"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/hqsync"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/inject"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/licensing"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/lifecycle"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/mail"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/models"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/paddle"
	sharedmail "gitea.hostxtra.co.uk/mrhid6/vantage/shared/mail"
	"github.com/joho/godotenv"
)

func main() {
	godotenv.Load()

	cfg, err := config.Load()
	if err != nil {
		log.Fatalf("configuration error: %v", err)
	}

	licensing.SetSigningKey(cfg.SigningKey)
	api.SetAppLoginURL(cfg.AppLoginURL)

	if _, err := paddle.Init(cfg.PaddleAPIKey, cfg.PaddleEnv); err != nil {
		log.Fatalf("paddle init: %v", err)
	}

	mail.Init(sharedmail.Sender{
		Host: cfg.SMTPHost, Port: cfg.SMTPPort, From: cfg.SMTPFrom,
		Username: cfg.SMTPUsername, Password: cfg.SMTPPassword,
		PublicURL: cfg.PublicURL,
	})
	if !mail.Enabled() {
		log.Println("warning: SMTP not configured; verification and licence emails will fail")
	}

	auth.InitRedis(cfg.RedisAddr, cfg.RedisUsername, cfg.RedisPassword)
	pingCtx, pingCancel := context.WithTimeout(context.Background(), 10*time.Second)
	if err := auth.Ping(pingCtx); err != nil {
		pingCancel()
		log.Fatalf("redis: %v", err)
	}
	pingCancel()

	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	if err := db.Connect(ctx, cfg); err != nil {
		cancel()
		log.Fatalf("database: %v", err)
	}
	cancel()
	log.Printf("connected: admin=%s control=%s", cfg.AdminDBName, cfg.ControlDBName)

	idxCtx, idxCancel := context.WithTimeout(context.Background(), 30*time.Second)
	if err := db.EnsureIndexes(idxCtx); err != nil {
		idxCancel()
		log.Fatalf("indexes: %v", err)
	}
	// Legacy plans are re-keyed BEFORE the seed, so the seed's fresh
	// (self_hosted, professional) row cannot collide with the legacy self_hosted
	// row's rename on deployment_tier_unique.
	if err := models.MigrateLegacyPlans(idxCtx); err != nil {
		idxCancel()
		log.Fatalf("migrate legacy plans: %v", err)
	}
	if err := models.SeedPlans(idxCtx); err != nil {
		idxCancel()
		log.Fatalf("plan seed: %v", err)
	}
	if err := models.SeedCatalogue(idxCtx); err != nil {
		idxCancel()
		log.Fatalf("seed catalogue: %v", err)
	}
	if err := models.Backfill(idxCtx); err != nil {
		idxCancel()
		log.Fatalf("backfill: %v", err)
	}
	idxCancel()

	reconcileCtx, stopReconcile := context.WithCancel(context.Background())
	defer stopReconcile()
	inject.StartReconciler(reconcileCtx)
	billing.StartPlaceholderReconciler(reconcileCtx)
	hqsync.Start(reconcileCtx)

	lifecycle.SetPortalURL(cfg.PublicURL)
	lifecycle.Start(reconcileCtx, cfg.ReapAfter)

	srv := &http.Server{
		Addr:              cfg.Addr,
		Handler:           api.Routes(cfg),
		ReadHeaderTimeout: 10 * time.Second,
		ReadTimeout:       20 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       60 * time.Second,
	}

	go func() {
		log.Printf("admin listening on %s", cfg.Addr)
		if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatalf("server error: %v", err)
		}
	}()

	stopCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()
	<-stopCtx.Done()

	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer shutdownCancel()
	_ = srv.Shutdown(shutdownCtx)
	log.Println("admin stopped")
	os.Exit(0)
}
