package bootstrap

import (
	"context"
	"fmt"
	"net/http"

	"github.com/italypaleale/francis/host/local"
	"github.com/pocket-id/pocket-id/backend/internal/api"
	"github.com/pocket-id/pocket-id/backend/internal/apikey"
	"github.com/pocket-id/pocket-id/backend/internal/appconfig"
	"github.com/pocket-id/pocket-id/backend/internal/auditlogs"
	"github.com/pocket-id/pocket-id/backend/internal/common"
	"github.com/pocket-id/pocket-id/backend/internal/devicelogin"
	"github.com/pocket-id/pocket-id/backend/internal/email"
	"github.com/pocket-id/pocket-id/backend/internal/emailverification"
	"github.com/pocket-id/pocket-id/backend/internal/geolite"
	"github.com/pocket-id/pocket-id/backend/internal/ldapsync"
	"github.com/pocket-id/pocket-id/backend/internal/oidc"
	"github.com/pocket-id/pocket-id/backend/internal/onetimeaccess"
	"github.com/pocket-id/pocket-id/backend/internal/scimsync"
	"github.com/pocket-id/pocket-id/backend/internal/service"
	"github.com/pocket-id/pocket-id/backend/internal/storage"
	"github.com/pocket-id/pocket-id/backend/internal/usersignup"
	"github.com/pocket-id/pocket-id/backend/internal/webauthn"
	"gorm.io/gorm"
)

type services struct {
	appConfigService   *appconfig.AppConfigService
	appImagesService   *service.AppImagesService
	emailModule        *email.Module
	geoLiteModule      *geolite.Module
	auditLogService    *service.AuditLogService
	jwtService         *service.JwtService
	userService        *service.UserService
	customClaimService *service.CustomClaimService
	oidcService        *service.OidcService
	userGroupService   *service.UserGroupService
	versionService     *service.VersionService
	fileStorage        storage.FileStorage

	apiKeyModule            *apikey.Module
	auditLogsModule         *auditlogs.Module
	deviceLoginModule       *devicelogin.Module
	ldapSyncModule          *ldapsync.Module
	scimSyncModule          *scimsync.Module
	oidcModule              *oidc.Module
	webauthnModule          *webauthn.Module
	userSignUpModule        *usersignup.Module
	oneTimeAccessModule     *onetimeaccess.Module
	emailVerificationModule *emailverification.Module
	apiModule               *api.Module
	actors                  *local.Host
}

// Initializes all services
func initServices(
	ctx context.Context,
	db *gorm.DB,
	instanceID string,
	actors *local.Host,
	httpClient *http.Client,
	imageExtensions map[string]string,
	fileStorage storage.FileStorage,
) (svc *services, err error) {
	svc = &services{
		actors: actors,
	}

	// Init the app config service
	svc.appConfigService, err = appconfig.NewService(ctx, actors, db)
	if err != nil {
		return nil, fmt.Errorf("failed to create app config service: %w", err)
	}

	svc.fileStorage = fileStorage
	svc.appImagesService = service.NewAppImagesService(imageExtensions, fileStorage)

	svc.emailModule, err = email.New(db)
	if err != nil {
		return nil, fmt.Errorf("failed to create email module: %w", err)
	}

	svc.geoLiteModule, err = geolite.New(ctx, geolite.Dependencies{
		HTTPClient:  httpClient,
		DBPath:      common.EnvConfig.GeoLiteDBPath,
		DownloadURL: common.EnvConfig.GeoLiteDBUrl,
		LicenseKey:  common.EnvConfig.MaxMindLicenseKey,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create GeoLite module: %w", err)
	}

	svc.auditLogService = service.NewAuditLogService(db, svc.emailModule, svc.geoLiteModule, svc.appConfigService)
	svc.auditLogsModule, err = auditlogs.New(auditlogs.Dependencies{
		DB:            db,
		Actors:        actors,
		RetentionDays: common.EnvConfig.AuditLogRetentionDays,
		// Disable in test environment
		CleanupDisabled: common.EnvConfig.AppEnv.IsTest(),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create audit logs module: %w", err)
	}

	svc.jwtService, err = service.NewJwtService(ctx, db, instanceID)
	if err != nil {
		return nil, fmt.Errorf("failed to create JWT service: %w", err)
	}

	svc.customClaimService = service.NewCustomClaimService(db)
	svc.webauthnModule, err = webauthn.New(webauthn.Dependencies{
		DB:        db,
		Actors:    actors,
		AppURL:    common.EnvConfig.AppURL,
		Signer:    svc.jwtService,
		AuditLog:  svc.auditLogService,
		AppConfig: svc.appConfigService,
		// Disable in test environment
		CleanupDisabled: common.EnvConfig.AppEnv.IsTest(),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create WebAuthn module: %w", err)
	}
	svc.deviceLoginModule, err = devicelogin.New(devicelogin.Dependencies{
		DB:        db,
		BaseURL:   common.EnvConfig.AppURL,
		Actors:    actors,
		Signer:    svc.jwtService,
		Reauth:    svc.webauthnModule,
		AuditLog:  svc.auditLogService,
		IPLocator: svc.geoLiteModule,
		AppConfig: svc.appConfigService,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create device login module: %w", err)
	}

	svc.scimSyncModule, err = scimsync.New(scimsync.Dependencies{
		DB:         db,
		Actors:     actors,
		HTTPClient: httpClient,
		// Disable in test environment
		ScheduleDisabled: common.EnvConfig.AppEnv.IsTest(),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create SCIM sync module: %w", err)
	}

	svc.apiModule = api.New(api.Dependencies{DB: db, Issuer: common.EnvConfig.AppURL})

	svc.oidcModule, err = oidc.New(ctx, oidc.Dependencies{
		DB:                  db,
		Actors:              actors,
		HTTPClient:          httpClient,
		GetCIMDURLAllowlist: svc.appConfigService.GetCIMDURLAllowlist,
		Config: oidc.Config{
			BaseURL:                   common.EnvConfig.AppURL,
			TokenBaseURL:              common.EnvConfig.AppURL,
			Secret:                    common.EnvConfig.EncryptionKey,
			AllowInsecureCallbackURLs: common.EnvConfig.AllowInsecureCallbackURLs,
		},
		Signer:       svc.jwtService,
		CustomClaims: svc.customClaimService,
		Reauth:       svc.webauthnModule,
		AuditLog:     svc.auditLogService,
		APIAccess:    svc.apiModule,
		// Disable in test environment
		CleanupDisabled: common.EnvConfig.AppEnv.IsTest(),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create OIDC module: %w", err)
	}

	svc.oidcService, err = service.NewOidcService(db, svc.jwtService, svc.oidcModule.Preview, svc.oidcModule, svc.scimSyncModule, httpClient, fileStorage)
	if err != nil {
		return nil, fmt.Errorf("failed to create OIDC service: %w", err)
	}

	svc.userGroupService = service.NewUserGroupService(db, svc.scimSyncModule)
	svc.userService = service.NewUserService(db, svc.jwtService, svc.auditLogService, svc.customClaimService, svc.appImagesService, svc.scimSyncModule, fileStorage)

	svc.ldapSyncModule, err = ldapsync.New(ldapsync.Dependencies{
		DB:          db,
		Actors:      actors,
		HTTPClient:  httpClient,
		FileStorage: fileStorage,
		Users:       svc.userService,
		Groups:      svc.userGroupService,
		AppConfig:   svc.appConfigService,
		ScimSync:    svc.scimSyncModule,
		// Disable in test environment
		ScheduleDisabled: common.EnvConfig.AppEnv.IsTest(),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create LDAP sync module: %w", err)
	}

	svc.apiKeyModule, err = apikey.New(ctx, apikey.Dependencies{
		DB:              db,
		Actors:          actors,
		StaticApiKey:    common.EnvConfig.StaticApiKey,
		AppConfig:       svc.appConfigService,
		EmailSender:     svc.emailModule,
		CleanupDisabled: common.EnvConfig.AppEnv.IsTest(),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create API key module: %w", err)
	}

	svc.userSignUpModule, err = usersignup.New(usersignup.Dependencies{
		DB:          db,
		Actors:      actors,
		Signer:      svc.jwtService,
		AuditLog:    svc.auditLogService,
		UserCreator: svc.userService,
		AppConfig:   svc.appConfigService,
		ScimSync:    svc.scimSyncModule,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create user signup module: %w", err)
	}

	svc.oneTimeAccessModule, err = onetimeaccess.New(onetimeaccess.Dependencies{
		DB:           db,
		Actors:       actors,
		Signer:       svc.jwtService,
		AuditLog:     svc.auditLogService,
		UserProvider: svc.userService,
		EmailSender:  svc.emailModule,
		AppConfig:    svc.appConfigService,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create one-time access module: %w", err)
	}

	svc.emailVerificationModule, err = emailverification.New(emailverification.Dependencies{
		DB:          db,
		Actors:      actors,
		Users:       svc.userService,
		EmailSender: svc.emailModule,
		AppConfig:   svc.appConfigService,
		AppURL:      common.EnvConfig.AppURL,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create email verification module: %w", err)
	}

	svc.versionService = service.NewVersionService(httpClient)

	return svc, nil
}
