package app

import (
	"context"
	"errors"
	"fmt"
	stdhttp "net/http"
	"time"

	"backupx/server/internal/backup"
	backupretention "backupx/server/internal/backup/retention"
	"backupx/server/internal/config"
	"backupx/server/internal/database"
	aphttp "backupx/server/internal/http"
	"backupx/server/internal/logger"
	"backupx/server/internal/notify"
	"backupx/server/internal/repository"
	"backupx/server/internal/scheduler"
	"backupx/server/internal/security"
	"backupx/server/internal/service"
	"backupx/server/internal/storage"
	"backupx/server/internal/storage/codec"
	"backupx/server/internal/storage/googledrive"
	"backupx/server/internal/storage/localdisk"
	storageAliyun "backupx/server/internal/storage/aliyun"
	storageFTP "backupx/server/internal/storage/ftp"
	storageTencent "backupx/server/internal/storage/tencent"
	storageQiniu "backupx/server/internal/storage/qiniu"
	storageS3 "backupx/server/internal/storage/s3"
	storageWebDAV "backupx/server/internal/storage/webdav"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type Application struct {
	cfg        config.Config
	version    string
	logger     *zap.Logger
	db         *gorm.DB
	httpServer *stdhttp.Server
	scheduler  *scheduler.Service
}

func New(ctx context.Context, cfg config.Config, version string) (*Application, error) {
	appLogger, err := logger.New(cfg.Log)
	if err != nil {
		return nil, fmt.Errorf("init logger: %w", err)
	}

	db, err := database.Open(cfg.Database, appLogger)
	if err != nil {
		return nil, fmt.Errorf("init database: %w", err)
	}

	userRepo := repository.NewUserRepository(db)
	systemConfigRepo := repository.NewSystemConfigRepository(db)
	storageTargetRepo := repository.NewStorageTargetRepository(db)
	backupTaskRepo := repository.NewBackupTaskRepository(db)
	backupRecordRepo := repository.NewBackupRecordRepository(db)
	notificationRepo := repository.NewNotificationRepository(db)
	oauthSessionRepo := repository.NewOAuthSessionRepository(db)
	resolvedSecurity, err := service.ResolveSecurity(ctx, cfg.Security, systemConfigRepo)
	if err != nil {
		return nil, fmt.Errorf("resolve security config: %w", err)
	}

	jwtManager := security.NewJWTManager(resolvedSecurity.JWTSecret, config.MustJWTDuration(cfg.Security))
	rateLimiter := security.NewLoginRateLimiter(5, time.Minute)
	authService := service.NewAuthService(userRepo, systemConfigRepo, jwtManager, rateLimiter)
	systemService := service.NewSystemService(cfg, version, time.Now().UTC())
	configCipher := codec.NewConfigCipher(resolvedSecurity.EncryptionKey)
	storageRegistry := storage.NewRegistry(
		localdisk.NewFactory(),
		storageS3.NewFactory(),
		storageWebDAV.NewFactory(),
		googledrive.NewFactory(),
		storageAliyun.NewFactory(),
		storageTencent.NewFactory(),
		storageQiniu.NewFactory(),
		storageFTP.NewFactory(),
	)
	storageTargetService := service.NewStorageTargetService(storageTargetRepo, oauthSessionRepo, storageRegistry, configCipher)
	storageTargetService.SetBackupTaskRepository(backupTaskRepo)
	storageTargetService.SetBackupRecordRepository(backupRecordRepo)
	backupTaskService := service.NewBackupTaskService(backupTaskRepo, storageTargetRepo, configCipher)
	backupRunnerRegistry := backup.NewRegistry(backup.NewFileRunner(), backup.NewSQLiteRunner(), backup.NewMySQLRunner(nil), backup.NewPostgreSQLRunner(nil), backup.NewSAPHANARunner(nil))
	logHub := backup.NewLogHub()
	retentionService := backupretention.NewService(backupRecordRepo)
	notifyRegistry := notify.NewRegistry(notify.NewEmailNotifier(), notify.NewWebhookNotifier(), notify.NewTelegramNotifier())
	notificationService := service.NewNotificationService(notificationRepo, notifyRegistry, configCipher)
	backupExecutionService := service.NewBackupExecutionService(backupTaskRepo, backupRecordRepo, storageTargetRepo, storageRegistry, backupRunnerRegistry, logHub, retentionService, configCipher, notificationService, cfg.Backup.TempDir, cfg.Backup.MaxConcurrent)
	schedulerService := scheduler.NewService(backupTaskRepo, backupExecutionService, appLogger)
	backupTaskService.SetScheduler(schedulerService)
	backupRecordService := service.NewBackupRecordService(backupRecordRepo, backupExecutionService, logHub)
	dashboardService := service.NewDashboardService(backupTaskRepo, backupRecordRepo, storageTargetRepo)
	settingsService := service.NewSettingsService(systemConfigRepo)

	// Audit
	auditLogRepo := repository.NewAuditLogRepository(db)
	auditService := service.NewAuditService(auditLogRepo)
	authService.SetAuditService(auditService)

	// Database discovery
	databaseDiscoveryService := service.NewDatabaseDiscoveryService(backup.NewOSCommandExecutor())

	// Cluster: Node management
	nodeRepo := repository.NewNodeRepository(db)
	nodeService := service.NewNodeService(nodeRepo)
	if err := nodeService.EnsureLocalNode(ctx); err != nil {
		appLogger.Warn("failed to ensure local node", zap.Error(err))
	}

	router := aphttp.NewRouter(aphttp.RouterDependencies{
		Config:                 cfg,
		Version:                version,
		Logger:                 appLogger,
		AuthService:            authService,
		SystemService:          systemService,
		StorageTargetService:   storageTargetService,
		BackupTaskService:      backupTaskService,
		BackupExecutionService: backupExecutionService,
		BackupRecordService:    backupRecordService,
		NotificationService:    notificationService,
		DashboardService:       dashboardService,
		SettingsService:        settingsService,
		NodeService:              nodeService,
		DatabaseDiscoveryService: databaseDiscoveryService,
		AuditService:            auditService,
		JWTManager:               jwtManager,
		UserRepository:         userRepo,
		SystemConfigRepo:       systemConfigRepo,
	})

	httpServer := &stdhttp.Server{
		Addr:              cfg.Address(),
		Handler:           router,
		ReadHeaderTimeout: 10 * time.Second,
	}

	return &Application{
		cfg:        cfg,
		version:    version,
		logger:     appLogger,
		db:         db,
		httpServer: httpServer,
		scheduler:  schedulerService,
	}, nil
}

func (a *Application) Run(ctx context.Context) error {
	if a.scheduler != nil {
		if err := a.scheduler.Start(context.Background()); err != nil {
			return fmt.Errorf("start scheduler: %w", err)
		}
	}
	errCh := make(chan error, 1)
	go func() {
		a.logger.Info("http server listening", zap.String("addr", a.cfg.Address()), zap.String("version", a.version))
		if err := a.httpServer.ListenAndServe(); err != nil && !errors.Is(err, stdhttp.ErrServerClosed) {
			errCh <- err
			return
		}
		errCh <- nil
	}()

	select {
	case <-ctx.Done():
		shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		a.logger.Info("shutdown signal received")
		if err := a.httpServer.Shutdown(shutdownCtx); err != nil {
			return fmt.Errorf("shutdown http server: %w", err)
		}
		if a.scheduler != nil {
			if err := a.scheduler.Stop(context.Background()); err != nil {
				return fmt.Errorf("stop scheduler: %w", err)
			}
		}
		return nil
	case err := <-errCh:
		if err != nil {
			return fmt.Errorf("serve http: %w", err)
		}
		return nil
	}
}

func (a *Application) Close() {
	if a.logger != nil {
		_ = a.logger.Sync()
	}
}

func (a *Application) Logger() *zap.Logger {
	return a.logger
}

func ErrorField(err error) zap.Field {
	return zap.Error(err)
}
