package app

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

	"eslogad-be/config"
	"eslogad-be/internal/client"
	internalConfig "eslogad-be/internal/config"
	"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)
	letterOutgoingHandler := handler.NewLetterOutgoingHandler(services.letterOutgoingService)
	adminApprovalFlowHandler := handler.NewAdminApprovalFlowHandler(services.approvalFlowService)
	dispositionRouteHandler := handler.NewDispositionRouteHandler(services.dispositionRouteService)
	onlyOfficeHandler := handler.NewOnlyOfficeHandler(services.onlyOfficeService)
	analyticsHandler := handler.NewAnalyticsHandler(services.analyticsService)
	notificationHandler := handler.NewNotificationHandler(services.notificationService)

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

	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
	// letter outgoing repos
	letterOutgoingRepo                 *repository.LetterOutgoingRepository
	letterOutgoingAttachmentRepo       *repository.LetterOutgoingAttachmentRepository
	letterOutgoingRecipientRepo        *repository.LetterOutgoingRecipientRepository
	letterOutgoingDiscussionRepo       *repository.LetterOutgoingDiscussionRepository
	letterOutgoingDiscussionAttachRepo *repository.LetterOutgoingDiscussionAttachmentRepository
	letterOutgoingActivityLogRepo      *repository.LetterOutgoingActivityLogRepository
	approvalFlowRepo                   *repository.ApprovalFlowRepository
	letterOutgoingApprovalRepo         *repository.LetterOutgoingApprovalRepository
	analyticsRepo                      *repository.AnalyticsRepository
}

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),
		letterOutgoingRepo:                 repository.NewLetterOutgoingRepository(a.db),
		letterOutgoingAttachmentRepo:       repository.NewLetterOutgoingAttachmentRepository(a.db),
		letterOutgoingRecipientRepo:        repository.NewLetterOutgoingRecipientRepository(a.db),
		letterOutgoingDiscussionRepo:       repository.NewLetterOutgoingDiscussionRepository(a.db),
		letterOutgoingDiscussionAttachRepo: repository.NewLetterOutgoingDiscussionAttachmentRepository(a.db),
		letterOutgoingActivityLogRepo:      repository.NewLetterOutgoingActivityLogRepository(a.db),
		approvalFlowRepo:                   repository.NewApprovalFlowRepository(a.db),
		letterOutgoingApprovalRepo:         repository.NewLetterOutgoingApprovalRepository(a.db),
		analyticsRepo:                      repository.NewAnalyticsRepository(a.db),
	}
}

type processors struct {
	userProcessor                  *processor.UserProcessorImpl
	cachedUserProcessor            *processor.CachedUserProcessor
	letterProcessor                *processor.LetterProcessorImpl
	letterOutgoingProcessor        *processor.LetterOutgoingProcessorImpl
	activityLogger                 *processor.ActivityLogProcessorImpl
	letterNumberGenerator          *processor.LetterNumberGeneratorImpl
	onlyOfficeProcessor            *processor.OnlyOfficeProcessorImpl
	novuProcessor                  processor.NovuProcessor
	notificationProcessor          processor.NotificationProcessor
	recipientProcessor             *processor.RecipientProcessorImpl
	letterDispositionProcessor     *processor.LetterDispositionProcessorImpl
	letterDispositionDeptProcessor *processor.LetterDispositionDepartmentProcessorImpl
}

func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
	txMgr := repository.NewTxManager(a.db)
	activity := processor.NewActivityLogProcessor(repos.activityLogRepo)

	// Create the letter number generator
	letterNumberGen := processor.NewLetterNumberGenerator(repos.settingRepo)

	// Create letter processors with the number generator
	letterProc := processor.NewLetterProcessor(
		repos.letterRepo, repos.letterAttachRepo, txMgr, activity,
		repos.letterDispositionRepo, repos.letterDispositionDeptRepo,
		repos.letterDispActionSelRepo, repos.dispositionNoteRepo,
		repos.letterDiscussionRepo, repos.settingRepo,
		repos.recipientRepo, repos.letterOutgoingRecipientRepo,
		repos.departmentRepo, repos.userDeptRepo,
		repos.priorityRepo, repos.institutionRepo, repos.dispRepo,
		letterNumberGen, repos.dispositionRouteRepo,
	)

	letterOutgoingProc := processor.NewLetterOutgoingProcessor(
		a.db,
		repos.letterOutgoingRepo,
		repos.letterOutgoingAttachmentRepo,
		repos.letterOutgoingRecipientRepo,
		repos.letterOutgoingDiscussionRepo,
		repos.letterOutgoingDiscussionAttachRepo,
		repos.letterOutgoingActivityLogRepo,
		repos.approvalFlowRepo,
		repos.letterOutgoingApprovalRepo,
		letterNumberGen,
		txMgr,
		repos.priorityRepo,
		repos.institutionRepo,
	)

	// Create document repositories
	docSessionRepo := repository.NewDocumentSessionRepository(a.db)
	docVersionRepo := repository.NewDocumentVersionRepository(a.db)
	docMetadataRepo := repository.NewDocumentMetadataRepository(a.db)
	docErrorRepo := repository.NewDocumentErrorRepository(a.db)

	// Create OnlyOffice processor
	onlyOfficeProc := processor.NewOnlyOfficeProcessor(
		a.db,
		docSessionRepo,
		docVersionRepo,
		docMetadataRepo,
		docErrorRepo,
		txMgr,
	)

	// Create Novu processor for backward compatibility
	novuConfig := internalConfig.LoadNovuConfig(cfg)
	novuProc := processor.NewNovuProcessor(novuConfig)
	
	// Create notification processor with Novu provider
	novuProvider := processor.NewNovuProvider(novuConfig)
	notificationProc := processor.NewNotificationProcessor(novuProvider, novuConfig.IncomingLetterWorkflowID)

	// Create user role processor
	userRoleProc := processor.NewUserRoleProcessor(a.db)
	
	// Create user processor with Novu integration
	userProc := processor.NewUserProcessor(repos.userRepo, repos.userProfileRepo, userRoleProc)
	userProc.SetNovuProcessor(novuProc)

	// Create cached user processor for auth middleware
	cachedUserProc := processor.NewCachedUserProcessor(repos.userRepo, repos.userProfileRepo, userRoleProc)

	// Create recipient processor
	recipientProc := processor.NewRecipientProcessor(
		repos.recipientRepo,
		repos.settingRepo,
		repos.departmentRepo,
		repos.userDeptRepo,
	)

	// Create letter disposition processor
	letterDispositionProc := processor.NewLetterDispositionProcessor(
		repos.letterDispositionRepo,
		repos.letterDispositionDeptRepo,
		repos.letterDispActionSelRepo,
		repos.dispositionNoteRepo,
		repos.letterDiscussionRepo,
		repos.dispRepo,
		activity,
	)

	// Create letter disposition department processor
	letterDispositionDeptProc := processor.NewLetterDispositionDepartmentProcessor(
		repos.letterDispositionDeptRepo,
		repos.dispositionNoteRepo,
		repos.letterRepo,
	)

	return &processors{
		userProcessor:                  userProc,
		cachedUserProcessor:            cachedUserProc,
		letterProcessor:                letterProc,
		letterOutgoingProcessor:        letterOutgoingProc,
		activityLogger:                 activity,
		letterNumberGenerator:          letterNumberGen,
		onlyOfficeProcessor:            onlyOfficeProc,
		novuProcessor:                  novuProc,
		notificationProcessor:          notificationProc,
		recipientProcessor:             recipientProc,
		letterDispositionProcessor:     letterDispositionProc,
		letterDispositionDeptProcessor: letterDispositionDeptProc,
	}
}

type services struct {
	userService             *service.UserServiceImpl
	authService             *service.AuthServiceImpl
	fileService             *service.FileServiceImpl
	rbacService             *service.RBACServiceImpl
	masterService           *service.MasterServiceImpl
	letterService           *service.LetterServiceImpl
	letterOutgoingService   *service.LetterOutgoingServiceImpl
	approvalFlowService     *service.ApprovalFlowServiceImpl
	dispositionRouteService *service.DispositionRouteServiceImpl
	onlyOfficeService       *service.OnlyOfficeServiceImpl
	analyticsService        *service.AnalyticsServiceImpl
	notificationService     *service.NotificationServiceImpl
}

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, repos.departmentRepo, cfg)

	txManager := repository.NewTxManager(a.db)
	letterSvc := service.NewLetterService(
		processors.letterProcessor,
		txManager,
		processors.letterNumberGenerator,
		processors.recipientProcessor,
		processors.activityLogger,
		processors.letterDispositionProcessor,
		processors.notificationProcessor,
		processors.activityLogger,
	)
	dispRouteSvc := service.NewDispositionRouteService(repos.dispositionRouteRepo)
	letterOutgoingSvc := service.NewLetterOutgoingService(processors.letterOutgoingProcessor)

	approvalFlowStepRepo := repository.NewApprovalFlowStepRepository(a.db)
	approvalFlowSvc := service.NewApprovalFlowService(
		a.db,
		repos.approvalFlowRepo,
		approvalFlowStepRepo,
		txManager,
	)

	// Create OnlyOffice service with file storage
	onlyOfficeSvc := service.NewOnlyOfficeService(processors.onlyOfficeProcessor, &cfg.OnlyOffice, a.db, s3Client)

	// Create Analytics service
	analyticsSvc := service.NewAnalyticsService(repos.analyticsRepo)

	// Create Notification service
	novuConfig := internalConfig.LoadNovuConfig(cfg)
	notificationSvc := service.NewNotificationService(novuConfig, processors.userProcessor)

	return &services{
		userService:             userSvc,
		authService:             authService,
		fileService:             fileSvc,
		rbacService:             rbacSvc,
		masterService:           masterSvc,
		letterService:           letterSvc,
		letterOutgoingService:   letterOutgoingSvc,
		approvalFlowService:     approvalFlowSvc,
		dispositionRouteService: dispRouteSvc,
		onlyOfficeService:       onlyOfficeSvc,
		analyticsService:        analyticsSvc,
		notificationService:     notificationSvc,
	}
}

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(),
	}
}
