package main

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

	"github.com/calendarapi/internal/api"
	"github.com/calendarapi/internal/api/handlers"
	"github.com/calendarapi/internal/auth"
	"github.com/calendarapi/internal/config"
	"github.com/calendarapi/internal/middleware"
	"github.com/calendarapi/internal/repository"
	"github.com/calendarapi/internal/scheduler"
	"github.com/calendarapi/internal/service"
	"github.com/golang-migrate/migrate/v4"
	_ "github.com/golang-migrate/migrate/v4/database/postgres"
	_ "github.com/golang-migrate/migrate/v4/source/file"
	"github.com/jackc/pgx/v5/pgxpool"
)

func main() {
	cfg := config.Load()

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	pool, err := pgxpool.New(ctx, cfg.DatabaseURL)
	if err != nil {
		log.Fatalf("connect to database: %v", err)
	}
	defer pool.Close()

	if err := pool.Ping(ctx); err != nil {
		log.Fatalf("ping database: %v", err)
	}
	log.Println("connected to PostgreSQL")

	runMigrations(cfg.DatabaseURL)

	queries := repository.New(pool)

	jwtManager := auth.NewJWTManager(cfg.JWTSecret)
	auditSvc := service.NewAuditService(queries)
	calSvc := service.NewCalendarService(pool, queries, auditSvc, cfg.BaseURL)

	var sched service.ReminderScheduler
	if cfg.RedisAddr != "" {
		sched = scheduler.NewScheduler(cfg.RedisAddr)
	} else {
		sched = scheduler.NoopScheduler{}
	}
	if realSched, ok := sched.(*scheduler.Scheduler); ok {
		defer realSched.Close()
	}

	eventSvc := service.NewEventService(pool, queries, calSvc, auditSvc, sched)

	if cfg.RedisAddr != "" {
		realSched := sched.(*scheduler.Scheduler)
		icsHandler := handlers.NewICSHandler(calSvc, eventSvc, queries)
		subSyncWorker := scheduler.NewSubscriptionSyncWorker(icsHandler)
		worker := scheduler.NewReminderWorker(queries)
		asynqSrv := scheduler.StartWorker(cfg.RedisAddr, worker, subSyncWorker)
		defer asynqSrv.Shutdown()

		enqueuerCtx, enqueuerCancel := context.WithCancel(context.Background())
		defer enqueuerCancel()
		go scheduler.StartSubscriptionEnqueuer(enqueuerCtx, queries, realSched, 15*time.Minute)

		log.Println("Redis connected, background jobs enabled")
	} else {
		log.Println("Redis not configured, background jobs disabled")
	}

	authSvc := service.NewAuthService(pool, queries, jwtManager, auditSvc)
	userSvc := service.NewUserService(pool, queries, auditSvc)
	contactSvc := service.NewContactService(queries, auditSvc)
	availSvc := service.NewAvailabilityService(queries, calSvc, eventSvc)
	bookingSvc := service.NewBookingService(pool, queries, calSvc, eventSvc)
	apiKeySvc := service.NewAPIKeyService(queries)
	reminderSvc := service.NewReminderService(queries, calSvc, sched)
	attendeeSvc := service.NewAttendeeService(queries, calSvc)

	h := api.Handlers{
		Auth:         handlers.NewAuthHandler(authSvc, userSvc),
		User:         handlers.NewUserHandler(userSvc),
		Calendar:     handlers.NewCalendarHandler(calSvc),
		Sharing:      handlers.NewSharingHandler(calSvc),
		Event:        handlers.NewEventHandler(eventSvc),
		Reminder:     handlers.NewReminderHandler(reminderSvc),
		Attendee:     handlers.NewAttendeeHandler(attendeeSvc),
		Contact:      handlers.NewContactHandler(contactSvc),
		Availability: handlers.NewAvailabilityHandler(availSvc),
		Booking:      handlers.NewBookingHandler(bookingSvc),
		APIKey:       handlers.NewAPIKeyHandler(apiKeySvc),
		ICS:          handlers.NewICSHandler(calSvc, eventSvc, queries),
	}

	authMW := middleware.NewAuthMiddleware(jwtManager, queries)
	rateLimiter := middleware.NewRateLimiter(cfg.RateLimitRPS, cfg.RateLimitBurst)

	router := api.NewRouter(h, authMW, rateLimiter, cfg)

	srv := &http.Server{
		Addr:         ":" + cfg.ServerPort,
		Handler:      router,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 15 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	go func() {
		log.Printf("server starting on port %s (env=%s)", cfg.ServerPort, cfg.Env)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("server error: %v", err)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("shutting down server...")

	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer shutdownCancel()

	if err := srv.Shutdown(shutdownCtx); err != nil {
		log.Fatalf("server shutdown error: %v", err)
	}
	log.Println("server stopped")
}

func runMigrations(databaseURL string) {
	m, err := migrate.New("file://migrations", databaseURL)
	if err != nil {
		log.Printf("migration init: %v (skipping)", err)
		return
	}
	defer m.Close()

	if err := m.Up(); err != nil && err != migrate.ErrNoChange {
		log.Fatalf("migration error: %v", err)
	}
	v, dirty, _ := m.Version()
	log.Printf("migrations applied (version=%d, dirty=%v)", v, dirty)
}
