package app

import (
	"apskel-pos-be/internal/client"
	"apskel-pos-be/internal/transformer"
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"apskel-pos-be/config"
	"apskel-pos-be/internal/handler"
	"apskel-pos-be/internal/middleware"
	"apskel-pos-be/internal/processor"
	"apskel-pos-be/internal/repository"
	"apskel-pos-be/internal/router"
	"apskel-pos-be/internal/service"
	"apskel-pos-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)
	validators := a.initValidators()
	middleware := a.initMiddleware(services)
	healthHandler := handler.NewHealthHandler()

	a.router = router.NewRouter(
		cfg,
		healthHandler,
		services.authService,
		middleware.authMiddleware,
		services.userService,
		validators.userValidator,
		services.organizationService,
		validators.organizationValidator,
		services.outletService,
		validators.outletValidator,
		services.outletSettingService,
		services.categoryService,
		validators.categoryValidator,
		services.productService,
		validators.productValidator,
		services.productVariantService,
		validators.productVariantValidator,
		services.inventoryService,
		validators.inventoryValidator,
		services.orderService,
		validators.orderValidator,
		services.fileService,
		validators.fileValidator,
		services.customerService,
		validators.customerValidator,
		services.paymentMethodService,
		validators.paymentMethodValidator,
		services.analyticsService,
		services.reportService,
		services.tableService,
		validators.tableValidator,
		services.unitService,
		services.ingredientService,
		services.productRecipeService,
		services.vendorService,
		validators.vendorValidator,
		services.purchaseOrderService,
		validators.purchaseOrderValidator,
		services.unitConverterService,
		validators.unitConverterValidator,
		services.chartOfAccountTypeService,
		validators.chartOfAccountTypeValidator,
		services.chartOfAccountService,
		validators.chartOfAccountValidator,
		services.accountService,
		validators.accountValidator,
	)

	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
	organizationRepo       *repository.OrganizationRepositoryImpl
	outletRepo             *repository.OutletRepositoryImpl
	outletSettingRepo      *repository.OutletSettingRepositoryImpl
	categoryRepo           *repository.CategoryRepositoryImpl
	productRepo            *repository.ProductRepositoryImpl
	productVariantRepo     *repository.ProductVariantRepositoryImpl
	inventoryRepo          *repository.InventoryRepositoryImpl
	inventoryMovementRepo  *repository.InventoryMovementRepositoryImpl
	orderRepo              *repository.OrderRepositoryImpl
	orderItemRepo          *repository.OrderItemRepositoryImpl
	paymentRepo            *repository.PaymentRepositoryImpl
	paymentOrderItemRepo   *repository.PaymentOrderItemRepositoryImpl
	paymentMethodRepo      *repository.PaymentMethodRepositoryImpl
	fileRepo               *repository.FileRepositoryImpl
	customerRepo           *repository.CustomerRepository
	analyticsRepo          *repository.AnalyticsRepositoryImpl
	tableRepo              *repository.TableRepository
	unitRepo               *repository.UnitRepository
	ingredientRepo         *repository.IngredientRepository
	productRecipeRepo      *repository.ProductRecipeRepository
	vendorRepo             *repository.VendorRepositoryImpl
	purchaseOrderRepo      *repository.PurchaseOrderRepositoryImpl
	unitConverterRepo      *repository.IngredientUnitConverterRepositoryImpl
	chartOfAccountTypeRepo *repository.ChartOfAccountTypeRepositoryImpl
	chartOfAccountRepo     *repository.ChartOfAccountRepositoryImpl
	accountRepo            *repository.AccountRepositoryImpl
	txManager              *repository.TxManager
}

func (a *App) initRepositories() *repositories {
	return &repositories{
		userRepo:               repository.NewUserRepository(a.db),
		organizationRepo:       repository.NewOrganizationRepositoryImpl(a.db),
		outletRepo:             repository.NewOutletRepositoryImpl(a.db),
		outletSettingRepo:      repository.NewOutletSettingRepositoryImpl(a.db),
		categoryRepo:           repository.NewCategoryRepositoryImpl(a.db),
		productRepo:            repository.NewProductRepositoryImpl(a.db),
		productVariantRepo:     repository.NewProductVariantRepositoryImpl(a.db),
		inventoryRepo:          repository.NewInventoryRepositoryImpl(a.db),
		inventoryMovementRepo:  repository.NewInventoryMovementRepositoryImpl(a.db),
		orderRepo:              repository.NewOrderRepositoryImpl(a.db),
		orderItemRepo:          repository.NewOrderItemRepositoryImpl(a.db),
		paymentRepo:            repository.NewPaymentRepositoryImpl(a.db),
		paymentOrderItemRepo:   repository.NewPaymentOrderItemRepositoryImpl(a.db),
		paymentMethodRepo:      repository.NewPaymentMethodRepositoryImpl(a.db),
		fileRepo:               repository.NewFileRepositoryImpl(a.db),
		customerRepo:           repository.NewCustomerRepository(a.db),
		analyticsRepo:          repository.NewAnalyticsRepositoryImpl(a.db),
		tableRepo:              repository.NewTableRepository(a.db),
		unitRepo:               repository.NewUnitRepository(a.db),
		ingredientRepo:         repository.NewIngredientRepository(a.db),
		productRecipeRepo:      repository.NewProductRecipeRepository(a.db),
		vendorRepo:             repository.NewVendorRepositoryImpl(a.db),
		purchaseOrderRepo:      repository.NewPurchaseOrderRepositoryImpl(a.db),
		unitConverterRepo:      repository.NewIngredientUnitConverterRepositoryImpl(a.db).(*repository.IngredientUnitConverterRepositoryImpl),
		chartOfAccountTypeRepo: repository.NewChartOfAccountTypeRepositoryImpl(a.db),
		chartOfAccountRepo:     repository.NewChartOfAccountRepositoryImpl(a.db),
		accountRepo:            repository.NewAccountRepositoryImpl(a.db),
		txManager:              repository.NewTxManager(a.db),
	}
}

type processors struct {
	userProcessor               *processor.UserProcessorImpl
	organizationProcessor       processor.OrganizationProcessor
	outletProcessor             processor.OutletProcessor
	outletSettingProcessor      *processor.OutletSettingProcessorImpl
	categoryProcessor           processor.CategoryProcessor
	productProcessor            processor.ProductProcessor
	productVariantProcessor     processor.ProductVariantProcessor
	inventoryProcessor          processor.InventoryProcessor
	orderProcessor              processor.OrderProcessor
	paymentMethodProcessor      processor.PaymentMethodProcessor
	fileProcessor               processor.FileProcessor
	customerProcessor           *processor.CustomerProcessor
	analyticsProcessor          *processor.AnalyticsProcessorImpl
	tableProcessor              *processor.TableProcessor
	unitProcessor               *processor.UnitProcessorImpl
	ingredientProcessor         *processor.IngredientProcessorImpl
	productRecipeProcessor      *processor.ProductRecipeProcessorImpl
	vendorProcessor             *processor.VendorProcessorImpl
	purchaseOrderProcessor      *processor.PurchaseOrderProcessorImpl
	unitConverterProcessor      *processor.IngredientUnitConverterProcessorImpl
	chartOfAccountTypeProcessor *processor.ChartOfAccountTypeProcessorImpl
	chartOfAccountProcessor     *processor.ChartOfAccountProcessorImpl
	accountProcessor            *processor.AccountProcessorImpl
	fileClient                  processor.FileClient
	inventoryMovementService    service.InventoryMovementService
}

func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
	fileClient := client.NewFileClient(cfg.S3Config)
	inventoryMovementService := service.NewInventoryMovementService(repos.inventoryMovementRepo, repos.ingredientRepo)

	return &processors{
		userProcessor:               processor.NewUserProcessor(repos.userRepo, repos.organizationRepo, repos.outletRepo),
		organizationProcessor:       processor.NewOrganizationProcessorImpl(repos.organizationRepo, repos.outletRepo, repos.userRepo),
		outletProcessor:             processor.NewOutletProcessorImpl(repos.outletRepo),
		outletSettingProcessor:      processor.NewOutletSettingProcessorImpl(repos.outletSettingRepo, repos.outletRepo),
		categoryProcessor:           processor.NewCategoryProcessorImpl(repos.categoryRepo),
		productProcessor:            processor.NewProductProcessorImpl(repos.productRepo, repos.categoryRepo, repos.productVariantRepo, repos.inventoryRepo, repos.outletRepo),
		productVariantProcessor:     processor.NewProductVariantProcessorImpl(repos.productVariantRepo, repos.productRepo),
		inventoryProcessor:          processor.NewInventoryProcessorImpl(repos.inventoryRepo, repos.productRepo, repos.outletRepo, repos.ingredientRepo, repos.inventoryMovementRepo),
		orderProcessor:              processor.NewOrderProcessorImpl(repos.orderRepo, repos.orderItemRepo, repos.paymentRepo, repos.paymentOrderItemRepo, repos.productRepo, repos.paymentMethodRepo, repos.inventoryRepo, repos.inventoryMovementRepo, repos.productVariantRepo, repos.outletRepo, repos.customerRepo, repos.txManager, repos.productRecipeRepo, repos.ingredientRepo, inventoryMovementService),
		paymentMethodProcessor:      processor.NewPaymentMethodProcessorImpl(repos.paymentMethodRepo),
		fileProcessor:               processor.NewFileProcessorImpl(repos.fileRepo, fileClient),
		customerProcessor:           processor.NewCustomerProcessor(repos.customerRepo),
		analyticsProcessor:          processor.NewAnalyticsProcessorImpl(repos.analyticsRepo),
		tableProcessor:              processor.NewTableProcessor(repos.tableRepo, repos.orderRepo),
		unitProcessor:               processor.NewUnitProcessor(repos.unitRepo),
		ingredientProcessor:         processor.NewIngredientProcessor(repos.ingredientRepo, repos.unitRepo),
		productRecipeProcessor:      processor.NewProductRecipeProcessor(repos.productRecipeRepo, repos.productRepo, repos.ingredientRepo),
		vendorProcessor:             processor.NewVendorProcessorImpl(repos.vendorRepo),
		purchaseOrderProcessor:      processor.NewPurchaseOrderProcessorImpl(repos.purchaseOrderRepo, repos.vendorRepo, repos.ingredientRepo, repos.unitRepo, repos.fileRepo, inventoryMovementService, repos.unitConverterRepo),
		unitConverterProcessor:      processor.NewIngredientUnitConverterProcessorImpl(repos.unitConverterRepo, repos.ingredientRepo, repos.unitRepo),
		chartOfAccountTypeProcessor: processor.NewChartOfAccountTypeProcessorImpl(repos.chartOfAccountTypeRepo),
		chartOfAccountProcessor:     processor.NewChartOfAccountProcessorImpl(repos.chartOfAccountRepo, repos.chartOfAccountTypeRepo),
		accountProcessor:            processor.NewAccountProcessorImpl(repos.accountRepo, repos.chartOfAccountRepo),
		fileClient:                  fileClient,
		inventoryMovementService:    inventoryMovementService,
	}
}

type services struct {
	userService               *service.UserServiceImpl
	authService               service.AuthService
	organizationService       service.OrganizationService
	outletService             service.OutletService
	outletSettingService      service.OutletSettingService
	categoryService           service.CategoryService
	productService            service.ProductService
	productVariantService     service.ProductVariantService
	inventoryService          service.InventoryService
	orderService              service.OrderService
	paymentMethodService      service.PaymentMethodService
	fileService               service.FileService
	customerService           service.CustomerService
	analyticsService          *service.AnalyticsServiceImpl
	reportService             service.ReportService
	tableService              *service.TableServiceImpl
	unitService               *service.UnitServiceImpl
	ingredientService         *service.IngredientServiceImpl
	productRecipeService      *service.ProductRecipeServiceImpl
	vendorService             *service.VendorServiceImpl
	purchaseOrderService      *service.PurchaseOrderServiceImpl
	unitConverterService      *service.IngredientUnitConverterServiceImpl
	chartOfAccountTypeService service.ChartOfAccountTypeService
	chartOfAccountService     service.ChartOfAccountService
	accountService            service.AccountService
}

func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
	authConfig := cfg.Auth()
	jwtSecret := authConfig.AccessTokenSecret()
	authService := service.NewAuthService(processors.userProcessor, jwtSecret)
	organizationService := service.NewOrganizationService(processors.organizationProcessor)
	outletService := service.NewOutletService(processors.outletProcessor)
	outletSettingService := service.NewOutletSettingService(processors.outletSettingProcessor)
	categoryService := service.NewCategoryService(processors.categoryProcessor)
	productService := service.NewProductService(processors.productProcessor)
	productVariantService := service.NewProductVariantService(processors.productVariantProcessor)
	inventoryService := service.NewInventoryService(processors.inventoryProcessor)
	orderService := service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo)
	paymentMethodService := service.NewPaymentMethodService(processors.paymentMethodProcessor)
	fileService := service.NewFileServiceImpl(processors.fileProcessor)
	var customerService service.CustomerService = service.NewCustomerService(processors.customerProcessor)
	analyticsService := service.NewAnalyticsServiceImpl(processors.analyticsProcessor)
	reportService := service.NewReportService(analyticsService, repos.organizationRepo, repos.outletRepo, processors.fileClient)
	tableService := service.NewTableService(processors.tableProcessor, transformer.NewTableTransformer())
	unitService := service.NewUnitService(processors.unitProcessor)
	ingredientService := service.NewIngredientService(processors.ingredientProcessor)
	productRecipeService := service.NewProductRecipeService(processors.productRecipeProcessor)
	vendorService := service.NewVendorService(processors.vendorProcessor)
	purchaseOrderService := service.NewPurchaseOrderService(processors.purchaseOrderProcessor)
	unitConverterService := service.NewIngredientUnitConverterService(processors.unitConverterProcessor)
	chartOfAccountTypeService := service.NewChartOfAccountTypeService(processors.chartOfAccountTypeProcessor)
	chartOfAccountService := service.NewChartOfAccountService(processors.chartOfAccountProcessor)
	accountService := service.NewAccountService(processors.accountProcessor)

	return &services{
		userService:               service.NewUserService(processors.userProcessor),
		authService:               authService,
		organizationService:       organizationService,
		outletService:             outletService,
		outletSettingService:      outletSettingService,
		categoryService:           categoryService,
		productService:            productService,
		productVariantService:     productVariantService,
		inventoryService:          inventoryService,
		orderService:              orderService,
		paymentMethodService:      paymentMethodService,
		fileService:               fileService,
		customerService:           customerService,
		analyticsService:          analyticsService,
		reportService:             reportService,
		tableService:              tableService,
		unitService:               unitService,
		ingredientService:         ingredientService,
		productRecipeService:      productRecipeService,
		vendorService:             vendorService,
		purchaseOrderService:      purchaseOrderService,
		unitConverterService:      unitConverterService,
		chartOfAccountTypeService: chartOfAccountTypeService,
		chartOfAccountService:     chartOfAccountService,
		accountService:            accountService,
	}
}

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
	organizationValidator       validator.OrganizationValidator
	outletValidator             validator.OutletValidator
	categoryValidator           validator.CategoryValidator
	productValidator            validator.ProductValidator
	productVariantValidator     validator.ProductVariantValidator
	inventoryValidator          validator.InventoryValidator
	orderValidator              validator.OrderValidator
	paymentMethodValidator      validator.PaymentMethodValidator
	fileValidator               validator.FileValidator
	customerValidator           validator.CustomerValidator
	tableValidator              *validator.TableValidator
	vendorValidator             *validator.VendorValidatorImpl
	purchaseOrderValidator      *validator.PurchaseOrderValidatorImpl
	unitConverterValidator      *validator.IngredientUnitConverterValidatorImpl
	chartOfAccountTypeValidator *validator.ChartOfAccountTypeValidatorImpl
	chartOfAccountValidator     *validator.ChartOfAccountValidatorImpl
	accountValidator            *validator.AccountValidatorImpl
}

func (a *App) initValidators() *validators {
	return &validators{
		userValidator:               validator.NewUserValidator(),
		organizationValidator:       validator.NewOrganizationValidator(),
		outletValidator:             validator.NewOutletValidator(),
		categoryValidator:           validator.NewCategoryValidator(),
		productValidator:            validator.NewProductValidator(),
		productVariantValidator:     validator.NewProductVariantValidator(),
		inventoryValidator:          validator.NewInventoryValidator(),
		orderValidator:              validator.NewOrderValidator(),
		paymentMethodValidator:      validator.NewPaymentMethodValidator(),
		fileValidator:               validator.NewFileValidatorImpl(),
		customerValidator:           validator.NewCustomerValidator(),
		tableValidator:              validator.NewTableValidator(),
		vendorValidator:             validator.NewVendorValidator(),
		purchaseOrderValidator:      validator.NewPurchaseOrderValidator(),
		unitConverterValidator:      validator.NewIngredientUnitConverterValidator().(*validator.IngredientUnitConverterValidatorImpl),
		chartOfAccountTypeValidator: validator.NewChartOfAccountTypeValidator().(*validator.ChartOfAccountTypeValidatorImpl),
		chartOfAccountValidator:     validator.NewChartOfAccountValidator().(*validator.ChartOfAccountValidatorImpl),
		accountValidator:            validator.NewAccountValidator().(*validator.AccountValidatorImpl),
	}
}
