package app

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

	"eslogad-be/config"
	"eslogad-be/internal/client"
	"eslogad-be/internal/handler"
	"eslogad-be/internal/middleware"
	"eslogad-be/internal/processor"
	"eslogad-be/internal/repository"
	"eslogad-be/internal/router"
	"eslogad-be/internal/service"
	"eslogad-be/internal/validator"

	"gorm.io/gorm"
)

type App struct {
	server   *http.Server
	db       *gorm.DB
	router   *router.Router
	shutdown chan os.Signal
}

func NewApp(db *gorm.DB) *App {
	return &App{
		db:       db,
		shutdown: make(chan os.Signal, 1),
	}
}

func (a *App) Initialize(cfg *config.Config) error {
	repos := a.initRepositories()
	processors := a.initProcessors(cfg, repos)
	services := a.initServices(processors, repos, cfg)
	middlewares := a.initMiddleware(services)
	healthHandler := handler.NewHealthHandler()
	fileHandler := handler.NewFileHandler(services.fileService)
	rbacHandler := handler.NewRBACHandler(services.rbacService)
	masterHandler := handler.NewMasterHandler(services.masterService)
	letterHandler := handler.NewLetterHandler(services.letterService)
	dispositionRouteHandler := handler.NewDispositionRouteHandler(services.dispositionRouteService)
	voteEventHandler := handler.NewVoteEventHandler(services.voteEventService)

	a.router = router.NewRouter(
		cfg,
		handler.NewAuthHandler(services.authService),
		middlewares.authMiddleware,
		healthHandler,
		handler.NewUserHandler(services.userService, validator.NewUserValidator()),
		fileHandler,
		rbacHandler,
		masterHandler,
		letterHandler,
		dispositionRouteHandler,
		voteEventHandler,
	)

	return nil
}

func (a *App) Start(port string) error {
	engine := a.router.Init()

	a.server = &http.Server{
		Addr:         ":" + port,
		Handler:      engine,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 15 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	signal.Notify(a.shutdown, os.Interrupt, syscall.SIGTERM)

	go func() {
		log.Printf("Server starting on port %s", port)
		if err := a.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("Failed to start server: %v", err)
		}
	}()

	<-a.shutdown
	log.Println("Shutting down server...")

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

	if err := a.server.Shutdown(ctx); err != nil {
		log.Printf("Server forced to shutdown: %v", err)
		return err
	}

	log.Println("Server exited gracefully")
	return nil
}

func (a *App) Shutdown() {
	close(a.shutdown)
}

type repositories struct {
	userRepo             *repository.UserRepositoryImpl
	userProfileRepo      *repository.UserProfileRepository
	titleRepo            *repository.TitleRepository
	rbacRepo             *repository.RBACRepository
	labelRepo            *repository.LabelRepository
	priorityRepo         *repository.PriorityRepository
	institutionRepo      *repository.InstitutionRepository
	dispRepo             *repository.DispositionActionRepository
	letterRepo           *repository.LetterIncomingRepository
	letterAttachRepo     *repository.LetterIncomingAttachmentRepository
	activityLogRepo      *repository.LetterIncomingActivityLogRepository
	dispositionRouteRepo *repository.DispositionRouteRepository
	// new repos
	letterDispositionRepo     *repository.LetterIncomingDispositionRepository
	letterDispositionDeptRepo *repository.LetterIncomingDispositionDepartmentRepository
	letterDispActionSelRepo   *repository.LetterDispositionActionSelectionRepository
	dispositionNoteRepo       *repository.DispositionNoteRepository
	letterDiscussionRepo      *repository.LetterDiscussionRepository
	settingRepo               *repository.AppSettingRepository
	recipientRepo             *repository.LetterIncomingRecipientRepository
	departmentRepo            *repository.DepartmentRepository
	userDeptRepo              *repository.UserDepartmentRepository
	voteEventRepo             *repository.VoteEventRepositoryImpl
}

func (a *App) initRepositories() *repositories {
	return &repositories{
		userRepo:                  repository.NewUserRepository(a.db),
		userProfileRepo:           repository.NewUserProfileRepository(a.db),
		titleRepo:                 repository.NewTitleRepository(a.db),
		rbacRepo:                  repository.NewRBACRepository(a.db),
		labelRepo:                 repository.NewLabelRepository(a.db),
		priorityRepo:              repository.NewPriorityRepository(a.db),
		institutionRepo:           repository.NewInstitutionRepository(a.db),
		dispRepo:                  repository.NewDispositionActionRepository(a.db),
		letterRepo:                repository.NewLetterIncomingRepository(a.db),
		letterAttachRepo:          repository.NewLetterIncomingAttachmentRepository(a.db),
		activityLogRepo:           repository.NewLetterIncomingActivityLogRepository(a.db),
		dispositionRouteRepo:      repository.NewDispositionRouteRepository(a.db),
		letterDispositionRepo:     repository.NewLetterIncomingDispositionRepository(a.db),
		letterDispositionDeptRepo: repository.NewLetterIncomingDispositionDepartmentRepository(a.db),
		letterDispActionSelRepo:   repository.NewLetterDispositionActionSelectionRepository(a.db),
		dispositionNoteRepo:       repository.NewDispositionNoteRepository(a.db),
		letterDiscussionRepo:      repository.NewLetterDiscussionRepository(a.db),
		settingRepo:               repository.NewAppSettingRepository(a.db),
		recipientRepo:             repository.NewLetterIncomingRecipientRepository(a.db),
		departmentRepo:            repository.NewDepartmentRepository(a.db),
		userDeptRepo:              repository.NewUserDepartmentRepository(a.db),
		voteEventRepo:             repository.NewVoteEventRepository(a.db),
	}
}

type processors struct {
	userProcessor   *processor.UserProcessorImpl
	letterProcessor *processor.LetterProcessorImpl
	activityLogger  *processor.ActivityLogProcessorImpl
}

func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
	txMgr := repository.NewTxManager(a.db)
	activity := processor.NewActivityLogProcessor(repos.activityLogRepo)
	return &processors{
		userProcessor:   processor.NewUserProcessor(repos.userRepo, repos.userProfileRepo),
		letterProcessor: processor.NewLetterProcessor(repos.letterRepo, repos.letterAttachRepo, txMgr, activity, repos.letterDispositionRepo, repos.letterDispositionDeptRepo, repos.letterDispActionSelRepo, repos.dispositionNoteRepo, repos.letterDiscussionRepo, repos.settingRepo, repos.recipientRepo, repos.departmentRepo, repos.userDeptRepo, repos.priorityRepo, repos.institutionRepo, repos.dispRepo),
		activityLogger:  activity,
	}
}

type services struct {
	userService             *service.UserServiceImpl
	authService             *service.AuthServiceImpl
	fileService             *service.FileServiceImpl
	rbacService             *service.RBACServiceImpl
	masterService           *service.MasterServiceImpl
	letterService           *service.LetterServiceImpl
	dispositionRouteService *service.DispositionRouteServiceImpl
	voteEventService        *service.VoteEventServiceImpl
}

func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
	authConfig := cfg.Auth()
	jwtSecret := authConfig.AccessTokenSecret()
	authService := service.NewAuthService(processors.userProcessor, jwtSecret)

	userSvc := service.NewUserService(processors.userProcessor, repos.titleRepo)

	fileCfg := cfg.S3Config
	s3Client := client.NewFileClient(fileCfg)
	fileSvc := service.NewFileService(s3Client, processors.userProcessor, "profile", "documents")

	rbacSvc := service.NewRBACService(repos.rbacRepo)

	masterSvc := service.NewMasterService(repos.labelRepo, repos.priorityRepo, repos.institutionRepo, repos.dispRepo)

	letterSvc := service.NewLetterService(processors.letterProcessor)
	dispRouteSvc := service.NewDispositionRouteService(repos.dispositionRouteRepo)
	voteEventSvc := service.NewVoteEventService(repos.voteEventRepo)

	return &services{
		userService:             userSvc,
		authService:             authService,
		fileService:             fileSvc,
		rbacService:             rbacSvc,
		masterService:           masterSvc,
		letterService:           letterSvc,
		dispositionRouteService: dispRouteSvc,
		voteEventService:        voteEventSvc,
	}
}

type middlewares struct {
	authMiddleware *middleware.AuthMiddleware
}

func (a *App) initMiddleware(services *services) *middlewares {
	return &middlewares{
		authMiddleware: middleware.NewAuthMiddleware(services.authService),
	}
}

type validators struct {
	userValidator *validator.UserValidatorImpl
}

func (a *App) initValidators() *validators {
	return &validators{
		userValidator: validator.NewUserValidator(),
	}
}
