package main

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"os"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
	"gorm.io/gorm"

	"wucher/internal/app/api"
	queueapp "wucher/internal/app/queueing"
	samlauth "wucher/internal/auth"
	"wucher/internal/config"
	filemanager "wucher/internal/domain/file_manager"
	filemanagerrealtime "wucher/internal/realtime/filemanager"
	helicopterfilerealtime "wucher/internal/realtime/helicopterfile"
	"wucher/internal/repository/mysql"
	s3repo "wucher/internal/repository/s3"
	"wucher/internal/resilience"
	"wucher/internal/service"
	pkglogger "wucher/internal/shared/pkg/logger"
	tzpkg "wucher/internal/shared/pkg/timezone"
	userctx "wucher/internal/shared/pkg/userctx"
	"wucher/internal/transport/http/handlers"
)

type Repositories struct {
	User                          *mysql.UserRepository
	Contact                       *mysql.ContactRepository
	Role                          *mysql.RoleRepository
	Helicopter                    *mysql.HelicopterRepository
	HelicopterUsage               *mysql.HelicopterUsageRepository
	FlightInspection              *mysql.FlightInspectionRepository
	Flight                        *mysql.FlightRepository
	FlightData                    *mysql.FlightDataRepository
	Mission                       *mysql.MissionRepository
	ReserveAc                     *mysql.ReserveAcRepository
	HelicopterFile                *mysql.HelicopterFileRepository
	FlightInspectionFileChecklist *mysql.FlightInspectionFileChecklistRepository
	BeforeFlightInspection        *mysql.BeforeFlightInspectionRepository
	AfterFlightInspection         *mysql.AfterFlightInspectionRepository
	FMReport                      *mysql.FMReportRepository
	FlightPrepCheck               *mysql.FlightPrepCheckRepository
	Facility                      *mysql.FacilityRepository
	Base                          *mysql.BaseRepository
	Medicine                      *mysql.MedicineRepository
	Vocation                      *mysql.VocationRepository
	Opc                           *mysql.OpcRepository
	ForcesPresent                 *mysql.ForcesPresentRepository
	Hospital                      *mysql.HospitalRepository
	HealthInsuranceCompanies      *mysql.HealthInsuranceCompaniesRepository
	FederalState                  *mysql.FederalStateRepository
	ICAO                          *mysql.ICAORepository
	Land                          *mysql.LandRepository
	Complaint                     *mysql.ComplaintRepository
	EASARelease                   *mysql.EASAReleaseRepository
	ActionSignoff                 *mysql.ActionSignoffRepository
	OtherPerson                   *mysql.OtherPersonRepository
	MCF                           *mysql.MCFRepository
	FleetHistory                  *mysql.FleetHistoryRepository
	HEMSOperationalData           *mysql.HEMSOperationalDataRepository
	HEMSOperationCategory         *mysql.HEMSOperationCategoryRepository
	HEMSOperation                 *mysql.HEMSOperationRepository
	MasterSettings                *mysql.MasterSettingsRepository
	FileManagerFolder             *mysql.FileManagerFolderRepository
	FileManagerFile               *mysql.FileManagerFileRepository
	FileManagerAttachment         *mysql.FileManagerAttachmentRepository
	DutyRoster                    *mysql.DutyRosterRepository
	AirRescuerChecklist           *mysql.AirRescuerChecklistRepository
	InsurancePatientData          *mysql.InsurancePatientDataRepository
	PatientData                   *mysql.PatientDataRepository
	DUL                           *mysql.DULRepository
	FleetStatus                   *mysql.FleetStatusRepository
	TokenStore                    *mysql.TokenStore
	Auth                          *mysql.AuthRepository
	Audit                         *mysql.AuditRepository
}

type Infrastructure struct {
	DB                *gorm.DB
	Logger            *slog.Logger
	SQSBreaker        *resilience.Executor
	FileStorage       filemanager.ObjectStorage
	EmailQueue        service.EmailQueue
	FileQueue         filemanager.FileProcessingQueue
	FileStatusHub     *filemanagerrealtime.Hub
	HelicopterFileHub *helicopterfilerealtime.Hub
	FileTrashPolicy   config.FileManagerTrashPolicyConfig
	FileLifecycle     config.FileManagerLifecycleConfig
}

type Services struct {
	User                      *service.UserService
	Contact                   *service.ContactService
	Role                      *service.RoleService
	Helicopter                *service.HelicopterService
	HelicopterUsage           *service.HelicopterUsageService
	FlightInspection          *service.FlightInspectionService
	Flight                    *service.FlightService
	FlightData                *service.FlightDataService
	Mission                   *service.MissionService
	ReserveAc                 *service.ReserveAcService
	Takeover                  *service.TakeoverService
	OtherPerson               *service.OtherPersonService
	HelicopterFile            *service.HelicopterFileService
	FlightInspectionChecklist *service.FlightInspectionFileChecklistService
	BeforeFlightInspection    *service.BeforeFlightInspectionService
	AfterFlightInspection     *service.AfterFlightInspectionService
	FMReport                  *service.FMReportService
	FlightPrepCheck           *service.FlightPrepCheckService
	Facility                  *service.FacilityService
	Base                      *service.BaseService
	Medicine                  *service.MedicineService
	Vocation                  *service.VocationService
	Opc                       *service.OpcService
	ForcesPresent             *service.ForcesPresentService
	Hospital                  *service.HospitalService
	HealthInsuranceCompanies  *service.HealthInsuranceCompaniesService
	FederalState              *service.FederalStateService
	ICAO                      *service.ICAOService
	Land                      *service.LandService
	Complaint                 *service.ComplaintService
	EASARelease               *service.EASAReleaseService
	ActionSignoff             *service.ActionSignoffService
	MCF                       *service.MCFService
	FleetHistory              *service.FleetHistoryService
	HEMSOperationalData       *service.HEMSOperationalDataService
	HEMSOperationCategory     *service.HEMSOperationCategoryService
	HEMSOperation             *service.HEMSOperationService
	MasterSettings            *service.MasterSettingsService
	FileManager               *service.FileManagerService
	DutyRoster                *service.DutyRosterService
	AirRescuerChecklist       *service.AirRescuerChecklistService
	InsurancePatientData      *service.InsurancePatientDataService
	PatientData               *service.PatientDataService
	DUL                       *service.DULService
	FleetStatus               *service.FleetStatusService
	WOPI                      *service.WOPIService
	Auth                      *service.AuthService
	Audit                     *service.AuditLogService
}

type Handlers struct {
	User                     *handlers.UserHandler
	Contact                  *handlers.ContactHandler
	Health                   *handlers.HealthHandler
	BuildInfo                *handlers.BuildInfoHandler
	Auth                     *handlers.AuthHandler
	Role                     *handlers.RoleHandler
	Helicopter               *handlers.HelicopterHandler
	HelicopterUsage          *handlers.HelicopterUsageHandler
	HelicopterFile           *handlers.HelicopterFileHandler
	Flight                   *handlers.FlightHandler
	FlightData               *handlers.FlightDataHandler
	Mission                  *handlers.MissionHandler
	FMReport                 *handlers.FMReportHandler
	ReserveAc                *handlers.ReserveAcHandler
	Takeover                 *handlers.TakeoverHandler
	OtherPerson              *handlers.OtherPersonHandler
	Facility                 *handlers.FacilityHandler
	Base                     *handlers.BaseHandler
	Medicine                 *handlers.MedicineHandler
	Vocation                 *handlers.VocationHandler
	Opc                      *handlers.OpcHandler
	ForcesPresent            *handlers.ForcesPresentHandler
	Hospital                 *handlers.HospitalHandler
	HealthInsuranceCompanies *handlers.HealthInsuranceCompaniesHandler
	FederalState             *handlers.FederalStateHandler
	ICAO                     *handlers.ICAOHandler
	Land                     *handlers.LandHandler
	MasterSettings           *handlers.MasterSettingsHandler
	Branding                 *handlers.BrandingHandler
	FileManagerFolder        *handlers.FileManagerFolderHandler
	FileManagerFile          *handlers.FileManagerFileHandler
	FileManagerAttachment    *handlers.FileManagerAttachmentHandler
	FileManagerEvents        *handlers.FileManagerEventsHandler
	HelicopterFileEvents     *handlers.HelicopterFileEventsHandler
	DutyRoster               *handlers.DutyRosterHandler
	AirRescuerChecklist      *handlers.AirRescuerChecklistHandler
	InsurancePatientData     *handlers.InsurancePatientDataHandler
	PatientData              *handlers.PatientDataHandler
	DUL                      *handlers.DULHandler
	FleetStatus              *handlers.FleetStatusHandler
	HEMSOperationalData      *handlers.HEMSOperationalDataHandler
	HEMSOperationCategory    *handlers.HEMSOperationCategoryHandler
	HEMSOperation            *handlers.HEMSOperationHandler
	Complaint                *handlers.ComplaintHandler
	EASARelease              *handlers.EASAReleaseHandler
	ActionSignoff            *handlers.ActionSignoffHandler
	MCF                      *handlers.MCFHandler
}

func initializeDatabase(mysqlCfg config.MySQLConfig) (*gorm.DB, error) {
	return mysql.Connect(mysqlCfg)
}

func bootstrapData(db *gorm.DB) error {
	if err := mysql.AutoMigrate(db); err != nil {
		return fmt.Errorf("auto migrate: %w", err)
	}
	if err := db.Exec("UPDATE users SET timezone = ? WHERE timezone IS NULL OR timezone = ''", tzpkg.Default).Error; err != nil {
		return fmt.Errorf("seed users timezone default: %w", err)
	}
	if err := api.SeedRoles(db); err != nil {
		return fmt.Errorf("seed roles: %w", err)
	}
	if err := api.SeedPermissions(db); err != nil {
		return fmt.Errorf("seed permissions: %w", err)
	}
	if err := api.SeedRolePermissions(db); err != nil {
		return fmt.Errorf("seed role permissions: %w", err)
	}
	if err := api.SeedBaseCategories(db); err != nil {
		return fmt.Errorf("seed base categories: %w", err)
	}
	if strings.EqualFold(strings.TrimSpace(os.Getenv("SEED_ROSTER_USERS")), "true") {
		if err := api.SeedRosterUsers(db); err != nil {
			return fmt.Errorf("seed roster users: %w", err)
		}
	}
	return nil
}

func initializeRepositories(db *gorm.DB) (*Repositories, error) {
	if db == nil {
		return nil, errors.New("database is required")
	}
	repos := &Repositories{
		User:                          mysql.NewUserRepository(db),
		Contact:                       mysql.NewContactRepository(db),
		Role:                          mysql.NewRoleRepository(db),
		Helicopter:                    mysql.NewHelicopterRepository(db),
		HelicopterUsage:               mysql.NewHelicopterUsageRepository(db),
		FlightInspection:              mysql.NewFlightInspectionRepository(db),
		Flight:                        mysql.NewFlightRepository(db),
		FlightData:                    mysql.NewFlightDataRepository(db),
		Mission:                       mysql.NewMissionRepository(db),
		ReserveAc:                     mysql.NewReserveAcRepository(db),
		HelicopterFile:                mysql.NewHelicopterFileRepository(db),
		FlightInspectionFileChecklist: mysql.NewFlightInspectionFileChecklistRepository(db),
		BeforeFlightInspection:        mysql.NewBeforeFlightInspectionRepository(db),
		AfterFlightInspection:         mysql.NewAfterFlightInspectionRepository(db),
		FMReport:                      mysql.NewFMReportRepository(db),
		FlightPrepCheck:               mysql.NewFlightPrepCheckRepository(db),
		Facility:                      mysql.NewFacilityRepository(db),
		Base:                          mysql.NewBaseRepository(db),
		Medicine:                      mysql.NewMedicineRepository(db),
		Vocation:                      mysql.NewVocationRepository(db),
		Opc:                           mysql.NewOpcRepository(db),
		ForcesPresent:                 mysql.NewForcesPresentRepository(db),
		Hospital:                      mysql.NewHospitalRepository(db),
		HealthInsuranceCompanies:      mysql.NewHealthInsuranceCompaniesRepository(db),
		FederalState:                  mysql.NewFederalStateRepository(db),
		ICAO:                          mysql.NewICAORepository(db),
		Land:                          mysql.NewLandRepository(db),
		Complaint:                     mysql.NewComplaintRepository(db),
		EASARelease:                   mysql.NewEASAReleaseRepository(db),
		ActionSignoff:                 mysql.NewActionSignoffRepository(db),
		OtherPerson:                   mysql.NewOtherPersonRepository(db),
		MCF:                           mysql.NewMCFRepository(db),
		FleetHistory:                  mysql.NewFleetHistoryRepository(db),
		HEMSOperationalData:           mysql.NewHEMSOperationalDataRepository(db),
		HEMSOperationCategory:         mysql.NewHEMSOperationCategoryRepository(db),
		HEMSOperation:                 mysql.NewHEMSOperationRepository(db),
		MasterSettings:                mysql.NewMasterSettingsRepository(db),
		FileManagerFolder:             mysql.NewFileManagerFolderRepository(db),
		FileManagerFile:               mysql.NewFileManagerFileRepository(db),
		FileManagerAttachment:         mysql.NewFileManagerAttachmentRepository(db),
		DutyRoster:                    mysql.NewDutyRosterRepository(db),
		AirRescuerChecklist:           mysql.NewAirRescuerChecklistRepository(db),
		InsurancePatientData:          mysql.NewInsurancePatientDataRepository(db),
		PatientData:                   mysql.NewPatientDataRepository(db),
		DUL:                           mysql.NewDULRepository(db),
		FleetStatus:                   mysql.NewFleetStatusRepository(db),
		TokenStore:                    mysql.NewTokenStore(db),
		Auth:                          mysql.NewAuthRepository(db),
		Audit:                         mysql.NewAuditRepository(db),
	}
	return repos, nil
}

func initializeInfrastructure(ctx context.Context, appCfg config.AppConfig, db *gorm.DB) (*Infrastructure, error) {
	appLogger := pkglogger.New(appCfg.Logging).With("component", "api")
	slog.SetDefault(appLogger)

	if err := config.ValidateDistinctQueueURLs(appCfg.SQS, appCfg.FileSQS); err != nil {
		return nil, err
	}

	resCfg := appCfg.Resilience
	sqsBreaker := resilience.NewExecutor("sqs", resCfg.SQS, appLogger, resilience.ClassifySQSError)

	var emailQueue service.EmailQueue
	if !appCfg.Queue.OutboxEnabled {
		emailProducer, err := queueapp.NewEmailProducer(ctx, appCfg.Queue, appCfg.SQS, sqsBreaker, appLogger)
		if err != nil {
			return nil, err
		}
		emailQueue = service.AdaptEmailQueue(emailProducer)
	}

	var fileStorage filemanager.ObjectStorage
	if appCfg.FileStorage.Bucket != "" && appCfg.FileStorage.Region != "" {
		storage, err := s3repo.NewFileObjectStorage(ctx, appCfg.FileStorage)
		if err != nil {
			return nil, err
		}
		fileStorage = storage
	}

	var fileQueue filemanager.FileProcessingQueue
	if !appCfg.FileQueue.OutboxEnabled && strings.TrimSpace(appCfg.FileSQS.QueueURL) != "" {
		fileProducer, err := queueapp.NewFileProcessingProducer(ctx, appCfg.FileQueue, appCfg.FileSQS, sqsBreaker, appLogger)
		if err != nil {
			return nil, err
		}
		fileQueue = service.AdaptFileProcessingQueue(fileProducer)
	}

	return &Infrastructure{
		DB:                db,
		Logger:            appLogger,
		SQSBreaker:        sqsBreaker,
		FileStorage:       fileStorage,
		EmailQueue:        emailQueue,
		FileQueue:         fileQueue,
		FileStatusHub:     filemanagerrealtime.NewHub(64, appLogger),
		HelicopterFileHub: helicopterfilerealtime.NewHub(64, appLogger),
		FileTrashPolicy:   config.LoadFileManagerTrashPolicyConfigFromEnv(),
		FileLifecycle:     config.LoadFileManagerLifecycleConfigFromEnv(),
	}, nil
}

func initializeServices(appCfg config.AppConfig, repos *Repositories, infra *Infrastructure) (*Services, error) {
	if repos == nil {
		return nil, errors.New("repositories are required")
	}
	if infra == nil {
		return nil, errors.New("infrastructure is required")
	}

	var protector service.SecretProtector
	if p, err := service.NewAESGCMProtectorFromBase64(appCfg.Auth.TOTPSecretKeyB64); err == nil {
		protector = p
	}

	fileManagerDeps := service.FileManagerServiceDependencies{
		Storage:          infra.FileStorage,
		FileQueue:        infra.FileQueue,
		UploadPolicy:     filemanager.FileUploadPolicy{},
		TrashRetention:   infra.FileTrashPolicy.Retention,
		FileStatusEvents: repos.FileManagerFile,
		Logger:           infra.Logger,
		EnableS3Tagging:  infra.FileLifecycle.EnableS3Tagging,
	}
	if appCfg.FileQueue.OutboxEnabled {
		fileManagerDeps.FileOutbox = repos.FileManagerFile
		fileManagerDeps.FileOutboxSerializer = queueapp.NewFileProcessingSerializer(appCfg.FileQueue, appCfg.FileSQS)
	}
	fileManagerDeps.UploadPolicy = filemanager.FileUploadPolicy{
		MaxFileSizeBytes:  appCfg.FileUpload.MaxFileSizeBytes,
		AllowedMimeTypes:  appCfg.FileUpload.AllowedMimeTypes,
		AllowedExtensions: appCfg.FileUpload.AllowedExtensions,
		AllowEmptyFile:    appCfg.FileUpload.AllowEmptyFile,
		MaxFilenameLength: appCfg.FileUpload.MaxFilenameLength,
	}

	flightDataSvc := service.NewFlightDataService(repos.FlightData)
	flightSvc := service.NewFlightService(repos.Flight)
	fileManagerSvc := service.NewFileManagerService(repos.FileManagerFolder, repos.FileManagerFile, repos.FileManagerAttachment, fileManagerDeps)
	helicopterSvc := service.NewHelicopterService(repos.Helicopter)
	helicopterUsageSvc := service.NewHelicopterUsageService(repos.HelicopterUsage)
	afterFlightInspectionSvc := service.NewAfterFlightInspectionService(repos.AfterFlightInspection)

	services := &Services{
		User:                      service.NewUserService(repos.User),
		Role:                      service.NewRoleService(repos.Role),
		Helicopter:                helicopterSvc,
		HelicopterUsage:           helicopterUsageSvc,
		FlightInspection:          service.NewFlightInspectionService(repos.FlightInspection),
		Flight:                    flightSvc,
		FlightData:                flightDataSvc,
		Mission:                   nil,
		ReserveAc:                 service.NewReserveAcService(repos.ReserveAc),
		Takeover:                  service.NewTakeoverService(infra.DB),
		HelicopterFile:            service.NewHelicopterFileService(repos.HelicopterFile),
		FlightInspectionChecklist: service.NewFlightInspectionFileChecklistService(repos.FlightInspectionFileChecklist),
		BeforeFlightInspection:    service.NewBeforeFlightInspectionService(repos.BeforeFlightInspection),
		AfterFlightInspection:     afterFlightInspectionSvc,
		FMReport:                  service.NewFMReportService(repos.FMReport),
		FlightPrepCheck:           service.NewFlightPrepCheckService(repos.FlightPrepCheck, repos.FlightInspection),
		Facility:                  service.NewFacilityService(repos.Facility),
		Base:                      service.NewBaseService(repos.Base),
		Medicine:                  service.NewMedicineService(repos.Medicine),
		Vocation:                  service.NewVocationService(repos.Vocation),
		Opc:                       service.NewOpcService(repos.Opc),
		ForcesPresent:             service.NewForcesPresentService(repos.ForcesPresent),
		Hospital:                  service.NewHospitalService(repos.Hospital),
		HealthInsuranceCompanies:  service.NewHealthInsuranceCompaniesService(repos.HealthInsuranceCompanies),
		FederalState:              service.NewFederalStateService(repos.FederalState),
		ICAO:                      service.NewICAOService(repos.ICAO),
		Land:                      service.NewLandService(repos.Land),
		Complaint:                 service.NewComplaintService(repos.Complaint),
		EASARelease:               service.NewEASAReleaseService(repos.EASARelease),
		ActionSignoff:             service.NewActionSignoffService(repos.ActionSignoff),
		OtherPerson:               service.NewOtherPersonService(repos.OtherPerson),
		MCF:                       service.NewMCFService(repos.MCF),
		FleetHistory:              service.NewFleetHistoryService(repos.FleetHistory),
		HEMSOperationalData:       service.NewHEMSOperationalDataService(repos.HEMSOperationalData),
		HEMSOperationCategory:     service.NewHEMSOperationCategoryService(repos.HEMSOperationCategory),
		HEMSOperation:             service.NewHEMSOperationService(repos.HEMSOperation),
		MasterSettings:            service.NewMasterSettingsService(repos.MasterSettings, service.MasterSettingsServiceDependencies{Protector: protector}),
		FileManager:               fileManagerSvc,
		DutyRoster:                service.NewDutyRosterService(repos.DutyRoster),
		AirRescuerChecklist:       service.NewAirRescuerChecklistService(repos.AirRescuerChecklist),
		InsurancePatientData:      service.NewInsurancePatientDataService(repos.InsurancePatientData),
		PatientData:               service.NewPatientDataService(repos.PatientData),
		DUL:                       service.NewDULService(repos.DUL),
		Audit:                     service.NewAuditLogService(repos.Audit, service.AuditLogServiceDependencies{QueueSize: appCfg.Audit.QueueSize, Workers: appCfg.Audit.Workers, IPEncryptionKey: appCfg.Auth.IPEncryptionKey}),
		WOPI: service.NewWOPIService(service.WOPIConfig{
			TokenSecret:   appCfg.WOPI.TokenSecret,
			ProofSecret:   appCfg.WOPI.ProofSecret,
			DiscoveryURL:  appCfg.WOPI.DiscoveryURL,
			PublicBaseURL: appCfg.WOPI.PublicBaseURL,
			EditActionURL: appCfg.WOPI.EditActionURL,
			TokenTTL:      appCfg.WOPI.TokenTTL,
			MaxBodyBytes:  appCfg.WOPI.MaxBodyBytes,
			RequireProof:  appCfg.WOPI.RequireProof,
		}),
	}
	services.Mission = service.NewMissionService(repos.Mission, flightSvc, flightDataSvc, afterFlightInspectionSvc)
	services.FleetStatus = service.NewFleetStatusService(repos.FleetStatus, fileManagerSvc, helicopterSvc, services.Complaint).WithHistory(services.FleetHistory).WithUsage(services.HelicopterUsage)
	flightDataSvc.WithAOGRecalculator(services.FleetStatus)
	flightDataSvc.WithMissionResolver(services.Mission)
	flightDataSvc.WithFlightLookup(services.Flight)
	flightDataSvc.WithFMReportLookup(services.FMReport)
	flightDataSvc.WithAfterFlightInspectionLookup(services.AfterFlightInspection)
	services.FMReport.WithUsageRefresher(services.HelicopterUsage)

	msSSO := service.NewMicrosoftSSO(appCfg.MicrosoftSSO).
		WithExecutor(resilience.NewExecutor("microsoft_sso", appCfg.Resilience.MicrosoftSSO, infra.Logger, resilience.ClassifyMicrosoftSSOError)).
		WithConfigResolver(func(ctx context.Context) (config.MicrosoftSSOConfig, error) {
			cfg, err := services.MasterSettings.LoadMicrosoftEntraRuntimeConfig(ctx)
			if err == nil {
				return cfg, nil
			}
			// Fallback for local/dev when master settings are missing or stale.
			// This prevents SSO flows from failing hard when DB-stored settings are invalid.
			if service.IsMicrosoftSSOConfigComplete(appCfg.MicrosoftSSO) {
				if infra.Logger != nil {
					infra.Logger.Warn("microsoft sso runtime config fallback to env", "reason", err.Error())
				}
				return appCfg.MicrosoftSSO, nil
			}
			return config.MicrosoftSSOConfig{}, err
		})

	authDeps := service.AuthServiceDependencies{
		SSO:                         msSSO,
		Protector:                   protector,
		Config:                      appCfg.Auth,
		Audit:                       services.Audit,
		EmailBranding:               service.NewAuthEmailBrandingResolver(services.MasterSettings, services.FileManager, infra.FileStorage),
		EmailOTPEnabled:             services.MasterSettings.IsLoginEmailOTPEnabled,
		DisableLegacyNoopEmailQueue: true,
	}
	if appCfg.Queue.OutboxEnabled {
		authDeps.EmailOutbox = repos.Auth
		authDeps.EmailSerializer = queueapp.NewOutboxSerializer(appCfg.Queue, appCfg.SQS)
	}
	services.Auth = service.NewAuthService(
		repos.Auth,
		repos.Role,
		repos.TokenStore,
		infra.EmailQueue,
		authDeps,
	)

	services.Contact = service.NewContactService(repos.Contact, services.Auth)
	services.HelicopterFile.
		WithFileManagerService(services.FileManager).
		WithHelicopterFileEvents(infra.HelicopterFileHub)
	if infra.FileStorage != nil && !appCfg.FileQueue.OutboxEnabled && strings.TrimSpace(appCfg.FileSQS.QueueURL) == "" {
		return nil, errors.New("FILE_SQS_QUEUE_URL is required when FILE_QUEUE_OUTBOX_ENABLED=false and file manager upload is enabled")
	}

	return services, nil
}

func initializeHandlers(services *Services, infra *Infrastructure) (*Handlers, error) {
	if services == nil {
		return nil, errors.New("services are required")
	}
	if infra == nil {
		return nil, errors.New("infrastructure is required")
	}

	fleetStatusHandler := handlers.NewFleetStatusHandler(services.FleetStatus, handlers.FleetStatusDeps{
		Helicopter: services.Helicopter,
		Complaint:  services.Complaint,
		EASA:       services.EASARelease,
		History:    services.FleetHistory,
		MCF:        services.MCF,
		Signoff:    services.ActionSignoff,
		Storage:    infra.FileStorage,
	})

	fmReportFlightHandler := handlers.NewFlightHandler(services.Flight).
		WithAuthService(services.Auth).
		WithAfterFlightInspectionService(services.AfterFlightInspection).
		WithFMReportService(services.FMReport).
		WithFlightDataService(services.FlightData).
		WithTakeoverService(service.NewTakeoverModuleService(mysql.NewTakeoverRepository(infra.DB))).
		WithHelicopterService(services.Helicopter).
		WithReserveAcService(services.ReserveAc).
		WithMissionService(services.Mission)

	services.FMReport.WithFleetHistoryBuilder(handlers.NewFMReportFleetHistoryBuilder(fleetStatusHandler, fmReportFlightHandler))
	services.FMReport.WithReportCodeResolver(fmReportFlightHandler)

	return &Handlers{
		User: handlers.NewUserHandler(services.User).
			WithFileManagerService(services.FileManager).
			WithFileStorage(infra.FileStorage),
		Contact: handlers.NewContactHandler(services.Contact, services.Auth).
			WithFileManagerService(services.FileManager).
			WithFileStorage(infra.FileStorage),
		Health:    handlers.NewHealthHandler(),
		BuildInfo: handlers.NewBuildInfoHandler(),
		Auth:      handlers.NewAuthHandler(services.Auth).WithFileStorage(infra.FileStorage).WithContactService(services.Contact),
		Role:      handlers.NewRoleHandler(services.Role),
		Helicopter: handlers.NewHelicopterHandler(services.Helicopter).
			WithFileManagerService(services.FileManager).
			WithFileStorage(infra.FileStorage).
			WithReserveAcService(services.ReserveAc).
			WithFleetStatusService(services.FleetStatus).
			WithPINVerifier(services.Auth).
			WithComplaintService(services.Complaint).
			WithMCFService(services.MCF).
			WithHistoryService(services.FleetHistory),
		HelicopterUsage: handlers.NewHelicopterUsageHandler(services.HelicopterUsage),
		HelicopterFile: handlers.NewHelicopterFileHandler(services.HelicopterFile).
			WithFileManagerService(services.FileManager).
			WithHelicopterService(services.Helicopter).
			WithTakeoverService(services.Takeover).
			WithHelicopterFileEvents(infra.HelicopterFileHub).
			WithFileStorage(infra.FileStorage),
		Flight: handlers.NewFlightHandler(services.Flight).
			WithAuthService(services.Auth).
			WithAfterFlightInspectionService(services.AfterFlightInspection).
			WithFMReportService(services.FMReport).
			WithFlightDataService(services.FlightData).
			WithTakeoverService(service.NewTakeoverModuleService(mysql.NewTakeoverRepository(infra.DB))).
			WithHelicopterService(services.Helicopter).
			WithReserveAcService(services.ReserveAc).
			WithMissionService(services.Mission),
		FlightData: handlers.NewFlightDataHandler(services.FlightData).
			WithMissionService(services.Mission).
			WithUserService(services.User).
			WithHospitalService(services.Hospital).
			WithICAOService(services.ICAO).
			WithFacilityService(services.Facility),
		Mission: handlers.NewMissionHandler(services.Mission).WithFlightDataService(services.FlightData).WithFileStorage(infra.FileStorage).WithFileManagerService(services.FileManager),
		FMReport: handlers.NewFMReportHandler(services.FMReport).
			WithFlightHandler(fmReportFlightHandler).
			WithMissionHandler(handlers.NewMissionHandler(services.Mission).WithFlightDataService(services.FlightData).WithFileStorage(infra.FileStorage).WithFileManagerService(services.FileManager)).
			WithFlightInspectionService(services.FlightInspection).
			WithAfterFlightInspectionService(services.AfterFlightInspection).
			WithHelicopterUsageService(services.HelicopterUsage).
			WithFleetStatusHandler(fleetStatusHandler).
			WithTakeoverService(services.Takeover).
			WithHelicopterService(services.Helicopter).
			WithFileStorage(infra.FileStorage).
			WithFileManagerService(services.FileManager),
		ReserveAc: handlers.NewReserveAcHandler(services.ReserveAc).
			WithFlightService(services.Flight).
			WithFlightDataService(services.FlightData).
			WithMissionService(services.Mission).
			WithDutyRosterService(services.DutyRoster).
			WithHelicopterService(services.Helicopter).
			WithFlightInspectionService(services.FlightInspection).
			WithBeforeFlightInspectionService(services.BeforeFlightInspection).
			WithAfterFlightInspectionService(services.AfterFlightInspection).
			WithFMReportService(services.FMReport).
			WithFlightPrepCheckService(services.FlightPrepCheck).
			WithHelicopterFileService(services.HelicopterFile).
			WithFlightInspectionFileChecklistService(services.FlightInspectionChecklist).
			WithFileStorage(infra.FileStorage),
		Takeover: handlers.NewTakeoverHandler(infra.DB).
			WithAuthService(services.Auth).
			WithBaseService(services.Base).
			WithFlightService(services.Flight).
			WithFileManagerService(services.FileManager).
			WithTakeoverAcService(service.NewTakeoverModuleService(mysql.NewTakeoverRepository(infra.DB))).
			WithReserveAcService(services.ReserveAc).
			WithDutyRosterService(services.DutyRoster).
			WithHelicopterService(services.Helicopter).
			WithComplaintService(services.Complaint).
			WithMCFService(services.MCF).
			WithFleetStatusService(services.FleetStatus).
			WithFlightInspectionService(services.FlightInspection).
			WithBeforeFlightInspectionService(services.BeforeFlightInspection).
			WithFlightPrepCheckService(services.FlightPrepCheck).
			WithHelicopterFileService(services.HelicopterFile).
			WithFlightInspectionFileChecklistService(services.FlightInspectionChecklist).
			WithFileStorage(infra.FileStorage),
		OtherPerson:              handlers.NewOtherPersonHandler(services.OtherPerson),
		Facility:                 handlers.NewFacilityHandler(services.Facility),
		Base:                     handlers.NewBaseHandler(services.Base).WithFileManagerService(services.FileManager).WithFileStorage(infra.FileStorage),
		Medicine:                 handlers.NewMedicineHandler(services.Medicine),
		Vocation:                 handlers.NewVocationHandler(services.Vocation),
		Opc:                      handlers.NewOpcHandler(services.Opc),
		ForcesPresent:            handlers.NewForcesPresentHandler(services.ForcesPresent),
		Hospital:                 handlers.NewHospitalHandler(services.Hospital),
		HealthInsuranceCompanies: handlers.NewHealthInsuranceCompaniesHandler(services.HealthInsuranceCompanies),
		FederalState:             handlers.NewFederalStateHandler(services.FederalState),
		ICAO:                     handlers.NewICAOHandler(services.ICAO),
		Land:                     handlers.NewLandHandler(services.Land),
		MasterSettings:           handlers.NewMasterSettingsHandler(services.MasterSettings, services.FileManager, infra.FileStorage),
		Branding:                 handlers.NewBrandingHandler(services.MasterSettings, services.FileManager, infra.FileStorage),
		FileManagerFolder:        handlers.NewFileManagerFolderHandler(services.FileManager, infra.FileStorage),
		FileManagerFile: handlers.NewFileManagerFileHandler(services.FileManager, infra.FileStorage).
			WithHelicopterFileService(services.HelicopterFile).
			WithAircraftService(services.Helicopter).
			WithBaseService(services.Base).
			WithDULService(services.DUL).
			WithAuthService(services.Auth).
			WithTakeoverService(services.Takeover).
			WithMissionService(services.Mission).
			WithUserService(services.User).
			WithFleetStatusFileService(service.NewFleetStatusFileService(services.FileManager)).
			WithHelicopterFileEvents(infra.HelicopterFileHub).
			WithWOPIService(services.WOPI),
		FileManagerAttachment: handlers.NewFileManagerAttachmentHandler(services.FileManager),
		FileManagerEvents:     handlers.NewFileManagerEventsHandler(infra.FileStatusHub),
		HelicopterFileEvents:  handlers.NewHelicopterFileEventsHandler(infra.HelicopterFileHub),
		DutyRoster:            handlers.NewDutyRosterHandler(services.DutyRoster).WithAudit(services.Audit).WithFlightService(services.Flight),
		AirRescuerChecklist:   handlers.NewAirRescuerChecklistHandler(services.AirRescuerChecklist),
		InsurancePatientData:  handlers.NewInsurancePatientDataHandler(services.InsurancePatientData),
		PatientData:           handlers.NewPatientDataHandler(services.PatientData),
		DUL:                   handlers.NewDULHandler(services.DUL).WithBaseService(services.Base).WithContactService(services.Contact).WithFileManagerService(services.FileManager).WithFileStorage(infra.FileStorage),
		FleetStatus:           fleetStatusHandler,
		HEMSOperationalData:   handlers.NewHEMSOperationalDataHandler(services.HEMSOperationalData).WithFileManagerService(services.FileManager),
		HEMSOperationCategory: handlers.NewHEMSOperationCategoryHandler(services.HEMSOperationCategory),
		HEMSOperation:         handlers.NewHEMSOperationHandler(services.HEMSOperation),
		Complaint:             handlers.NewComplaintHandler(services.Complaint).WithHelicopterService(services.Helicopter).WithMCFService(services.MCF).WithHistoryService(services.FleetHistory).WithAOGRecalculator(services.FleetStatus).WithActionSignoffService(services.ActionSignoff),
		EASARelease:           handlers.NewEASAReleaseHandler(services.EASARelease).WithComplaintActionChecker(services.Complaint).WithActionSignoffChecker(services.ActionSignoff).WithHistoryService(services.FleetHistory).WithContactResolver(services.Contact).WithAOGRecalculator(services.FleetStatus),
		ActionSignoff:         handlers.NewActionSignoffHandler(services.ActionSignoff).WithComplaintService(services.Complaint),
		MCF:                   handlers.NewMCFHandler(services.MCF).WithHistoryService(services.FleetHistory),
	}, nil
}

func initializeRealtimeComponents(
	ctx context.Context,
	repos *Repositories,
	services *Services,
	infra *Infrastructure,
) error {
	if repos == nil || services == nil || infra == nil {
		return errors.New("repositories, services, and infrastructure are required")
	}
	userNameCache := userctx.NewUserNameCache(repos.User, 5*time.Minute)
	handlers.WireUserNameGetter(userNameCache)

	startFileManagerAutoPurgeLoop(ctx, services.FileManager, infra.FileTrashPolicy, infra.Logger)
	startFileManagerLifecycleCleanupLoop(ctx, services.FileManager, infra.FileLifecycle, infra.Logger)
	startHelicopterFileAttachmentReconcileLoop(ctx, services.HelicopterFile, infra.Logger)
	startFleetStatusAOGRecomputeLoop(ctx, services.FleetStatus, infra.Logger)

	fileStatusPoller := filemanagerrealtime.NewStatusPoller(infra.DB, infra.FileStatusHub, infra.Logger, filemanagerrealtime.StatusPollerConfig{
		// Keep the SSE ready event responsive; this poller is the bridge between
		// the async file processor and the live file.updated stream.
		PollInterval: 250 * time.Millisecond,
		BatchSize:    100,
		OnPublished: func(ctx context.Context, event filemanagerrealtime.FileStatusEvent) error {
			if filemanager.NormalizeFileStatus(event.Status) != filemanager.FileStatusReady {
				return nil
			}
			if services == nil || services.HelicopterFile == nil {
				return nil
			}
			finalized, err := services.HelicopterFile.FinalizePendingAttachmentsBySourceFileUUID(ctx, event.FileUUID, 50)
			if err != nil {
				return err
			}
			if finalized > 0 && infra.Logger != nil {
				infra.Logger.Info("helicopter file attachments finalized from file ready event", "file_uuid", event.FileUUID, "finalized", finalized)
			}
			return nil
		},
	})
	go func() {
		if err := fileStatusPoller.Run(ctx); err != nil && ctx.Err() == nil {
			infra.Logger.Error("file status poller stopped unexpectedly", "error", err)
		}
	}()

	return nil
}

func buildHTTPServer(appCfg config.AppConfig, services *Services, handlersSet *Handlers, infra *Infrastructure) (*fiber.App, string, error) {
	if services == nil || handlersSet == nil || infra == nil {
		return nil, "", errors.New("services, handlers, and infra are required")
	}
	httpCfg := appCfg.HTTP
	app := fiber.New(fiber.Config{
		BodyLimit:    httpCfg.BodyLimit,
		ReadTimeout:  httpCfg.ReadTimeout,
		WriteTimeout: httpCfg.WriteTimeout,
		IdleTimeout:  httpCfg.IdleTimeout,
	})
	api.UseDefaultMiddlewares(app, httpCfg)

	envCfg := config.LoadEnvConfig()
	hasJSONSAML := appCfg.SAML.CertFile != "" ||
		appCfg.SAML.KeyFile != "" ||
		appCfg.SAML.MetadataFile != "" ||
		appCfg.SAML.RootURL != "" ||
		appCfg.SAML.FrontendURL != "" ||
		appCfg.SAML.CookieDomain != "" ||
		appCfg.SAML.SessionTTLHr > 0
	if appCfg.SAML.CertFile != "" {
		envCfg.SAMLCertFile = appCfg.SAML.CertFile
	}
	if appCfg.SAML.KeyFile != "" {
		envCfg.SAMLKeyFile = appCfg.SAML.KeyFile
	}
	if appCfg.SAML.MetadataFile != "" {
		envCfg.SAMLMetadataFile = appCfg.SAML.MetadataFile
	}
	if appCfg.SAML.RootURL != "" {
		envCfg.SAMLRootURL = appCfg.SAML.RootURL
	}
	if appCfg.SAML.FrontendURL != "" {
		envCfg.FrontendURL = appCfg.SAML.FrontendURL
	}
	if appCfg.SAML.CookieDomain != "" {
		envCfg.CookieDomain = appCfg.SAML.CookieDomain
	}
	if hasJSONSAML {
		envCfg.CookieSecure = appCfg.SAML.CookieSecure
	}
	if appCfg.SAML.SessionTTLHr > 0 {
		envCfg.SessionTTLHour = appCfg.SAML.SessionTTLHr
	}

	// Microsoft auth mode selector. Default "oauth" uses the OIDC/OAuth2 authorization
	// code flow (Microsoft refresh tokens are stored in DB and validated on
	// /api/v1/auth/refresh). Set MICROSOFT_AUTH_MODE=saml to fall back to the legacy
	// SAML flow (kept intact for rollback).
	microsoftAuthMode := strings.ToLower(strings.TrimSpace(os.Getenv("MICROSOFT_AUTH_MODE")))
	if microsoftAuthMode == "" {
		microsoftAuthMode = "oauth"
	}

	var samlHandler *samlauth.Handler
	if microsoftAuthMode == "saml" {
		if err := samlauth.InitSessionStoreDB(infra.DB); err == nil {
			if err := samlauth.InitSAML(envCfg); err == nil {
				samlHandler = samlauth.NewHandler(envCfg, services.Auth)
			}
		}
	}
	slog.Info("microsoft auth mode configured", slog.String("mode", microsoftAuthMode), slog.Bool("saml_active", samlHandler != nil))

	api.RegisterRoutes(app, api.RouteDependencies{
		Handlers: api.RouteHandlers{
			User:                     handlersSet.User,
			Contact:                  handlersSet.Contact,
			Health:                   handlersSet.Health,
			BuildInfo:                handlersSet.BuildInfo,
			Auth:                     handlersSet.Auth,
			Role:                     handlersSet.Role,
			Helicopter:               handlersSet.Helicopter,
			HelicopterUsage:          handlersSet.HelicopterUsage,
			HelicopterFile:           handlersSet.HelicopterFile,
			Flight:                   handlersSet.Flight,
			FlightData:               handlersSet.FlightData,
			Mission:                  handlersSet.Mission,
			FMReport:                 handlersSet.FMReport,
			ReserveAc:                handlersSet.ReserveAc,
			Takeover:                 handlersSet.Takeover,
			OtherPerson:              handlersSet.OtherPerson,
			Facility:                 handlersSet.Facility,
			Base:                     handlersSet.Base,
			Medicine:                 handlersSet.Medicine,
			Vocation:                 handlersSet.Vocation,
			Opc:                      handlersSet.Opc,
			ForcesPresent:            handlersSet.ForcesPresent,
			Hospital:                 handlersSet.Hospital,
			HealthInsuranceCompanies: handlersSet.HealthInsuranceCompanies,
			FederalState:             handlersSet.FederalState,
			ICAO:                     handlersSet.ICAO,
			Land:                     handlersSet.Land,
			MasterSettings:           handlersSet.MasterSettings,
			Branding:                 handlersSet.Branding,
			FileManagerFolder:        handlersSet.FileManagerFolder,
			FileManagerFile:          handlersSet.FileManagerFile,
			FileManagerAttachment:    handlersSet.FileManagerAttachment,
			FileManagerEvents:        handlersSet.FileManagerEvents,
			HelicopterFileEvents:     handlersSet.HelicopterFileEvents,
			DutyRoster:               handlersSet.DutyRoster,
			AirRescuerChecklist:      handlersSet.AirRescuerChecklist,
			InsurancePatientData:     handlersSet.InsurancePatientData,
			PatientData:              handlersSet.PatientData,
			DUL:                      handlersSet.DUL,
			FleetStatus:              handlersSet.FleetStatus,
			HEMSOperationalData:      handlersSet.HEMSOperationalData,
			HEMSOperationCategory:    handlersSet.HEMSOperationCategory,
			HEMSOperation:            handlersSet.HEMSOperation,
			Complaint:                handlersSet.Complaint,
			EASARelease:              handlersSet.EASARelease,
			ActionSignoff:            handlersSet.ActionSignoff,
			MCF:                      handlersSet.MCF,
			SAMLAuth:                 samlHandler,
		},
		Services: api.RouteServices{
			Auth:  services.Auth,
			Audit: services.Audit,
			WOPI:  services.WOPI,
		},
	})

	addr := ":8080"
	if appCfg.HTTPPort > 0 {
		addr = fmt.Sprintf(":%d", appCfg.HTTPPort)
	}
	return app, addr, nil
}
