package main

import (
	"context"
	"log/slog"
	"net/http"
	"os"
	"time"

	"github.com/jackc/pgx/v5/stdlib"
	"github.com/pressly/goose/v3"

	"tanabata/backend/internal/config"
	"tanabata/backend/internal/db/postgres"
	"tanabata/backend/internal/handler"
	"tanabata/backend/internal/service"
	"tanabata/backend/internal/storage"
	"tanabata/backend/migrations"
)

func main() {
	cfg, err := config.Load()
	if err != nil {
		slog.Error("failed to load config", "err", err)
		os.Exit(1)
	}

	pool, err := postgres.NewPool(context.Background(), cfg.DatabaseURL)
	if err != nil {
		slog.Error("failed to connect to database", "err", err)
		os.Exit(1)
	}
	defer pool.Close()
	slog.Info("database connected")

	migDB := stdlib.OpenDBFromPool(pool)
	goose.SetBaseFS(migrations.FS)
	if err := goose.SetDialect("postgres"); err != nil {
		slog.Error("goose dialect error", "err", err)
		os.Exit(1)
	}
	if err := goose.Up(migDB, "."); err != nil {
		slog.Error("migrations failed", "err", err)
		os.Exit(1)
	}
	migDB.Close()
	slog.Info("migrations applied")

	// Storage
	diskStorage, err := storage.NewDiskStorage(
		cfg.FilesPath,
		cfg.ThumbsCachePath,
		cfg.ThumbWidth, cfg.ThumbHeight,
		cfg.PreviewWidth, cfg.PreviewHeight,
	)
	if err != nil {
		slog.Error("failed to initialise storage", "err", err)
		os.Exit(1)
	}

	// Repositories
	userRepo     := postgres.NewUserRepo(pool)
	sessionRepo  := postgres.NewSessionRepo(pool)
	fileRepo     := postgres.NewFileRepo(pool)
	mimeRepo     := postgres.NewMimeRepo(pool)
	aclRepo      := postgres.NewACLRepo(pool)
	auditRepo    := postgres.NewAuditRepo(pool)
	tagRepo      := postgres.NewTagRepo(pool)
	tagRuleRepo  := postgres.NewTagRuleRepo(pool)
	categoryRepo := postgres.NewCategoryRepo(pool)
	poolRepo     := postgres.NewPoolRepo(pool)
	transactor   := postgres.NewTransactor(pool)

	// Services
	authSvc := service.NewAuthService(
		userRepo,
		sessionRepo,
		cfg.JWTSecret,
		cfg.JWTAccessTTL,
		cfg.JWTRefreshTTL,
	)
	aclSvc      := service.NewACLService(aclRepo, fileRepo, tagRepo, categoryRepo, poolRepo, transactor)
	auditSvc    := service.NewAuditService(auditRepo)
	tagSvc      := service.NewTagService(tagRepo, tagRuleRepo, aclSvc, auditSvc, transactor)
	categorySvc := service.NewCategoryService(categoryRepo, tagRepo, aclSvc, auditSvc)
	poolSvc     := service.NewPoolService(poolRepo, aclSvc, auditSvc)
	fileSvc     := service.NewFileService(
		fileRepo,
		mimeRepo,
		diskStorage,
		aclSvc,
		auditSvc,
		tagSvc,
		transactor,
		cfg.ImportPath,
	)
	userSvc := service.NewUserService(userRepo, sessionRepo, auditSvc)

	// Bootstrap the initial administrator (idempotent).
	if err := userSvc.EnsureAdmin(context.Background(), cfg.AdminUsername, cfg.AdminPassword); err != nil {
		slog.Error("failed to bootstrap admin user", "err", err)
		os.Exit(1)
	}

	// Handlers
	authMiddleware  := handler.NewAuthMiddleware(authSvc)
	authHandler     := handler.NewAuthHandler(authSvc)
	fileHandler     := handler.NewFileHandler(fileSvc, tagSvc, cfg.MaxUploadBytes)
	tagHandler      := handler.NewTagHandler(tagSvc, fileSvc)
	categoryHandler := handler.NewCategoryHandler(categorySvc)
	poolHandler     := handler.NewPoolHandler(poolSvc)
	userHandler     := handler.NewUserHandler(userSvc)
	aclHandler      := handler.NewACLHandler(aclSvc)
	auditHandler    := handler.NewAuditHandler(auditSvc)

	r := handler.NewRouter(
		authMiddleware, authHandler,
		fileHandler, tagHandler, categoryHandler, poolHandler,
		userHandler, aclHandler, auditHandler,
	)

	// ReadHeaderTimeout bounds slow-header (Slowloris) attacks; body read/write
	// are left unbounded so large file uploads and downloads can stream.
	srv := &http.Server{
		Addr:              cfg.ListenAddr,
		Handler:           r,
		ReadHeaderTimeout: 10 * time.Second,
		IdleTimeout:       120 * time.Second,
	}

	slog.Info("starting server", "addr", cfg.ListenAddr)
	if err := srv.ListenAndServe(); err != nil {
		slog.Error("server error", "err", err)
		os.Exit(1)
	}
}
