package usecase

import (
	"fmt"

	libcrypto "gateway/internal/library/crypto"
	libmongo "gateway/internal/library/mongo"
	redislib "gateway/internal/library/redis"
	memberconfig "gateway/internal/model/member/config"
	domrepo "gateway/internal/model/member/domain/repository"
	domusecase "gateway/internal/model/member/domain/usecase"
	"gateway/internal/model/member/repository"
)

// Module bundles member atomic primitives. Each entry is a single-purpose
// usecase; composite flows (e.g. "send verification email then mark
// business_email verified") are assembled at the logic / driver layer and
// MUST NOT live inside another usecase.
type Module struct {
	OTP          domusecase.OTPUseCase
	TOTP         domusecase.TOTPUseCase
	StepUp       domusecase.StepUpUseCase
	Profile      domusecase.ProfileUseCase
	Lifecycle    domusecase.LifecycleUseCase
	Provisioning domusecase.ProvisioningUseCase
	Tenant       domusecase.TenantUseCase
	VerifyRate   domusecase.VerifyRateUseCase

	Members    domrepo.MemberRepository
	Tenants    domrepo.TenantRepository
	Identities domrepo.IdentityRepository
}

// ModuleParam wires member module dependencies.
type ModuleParam struct {
	Redis     *redislib.Client
	MongoConf *libmongo.Conf
	Config    memberconfig.Config
	// Optional overrides for tests.
	Members     domrepo.MemberRepository
	Tenants     domrepo.TenantRepository
	Identities  domrepo.IdentityRepository
	TOTPProfile domrepo.TOTPProfileRepository
	UIDGen      domrepo.UIDGenerator
}

// NewModuleFromParam builds member atomic usecases.
func NewModuleFromParam(param ModuleParam) (*Module, error) {
	if param.Redis == nil || param.Redis.Zero() == nil {
		return nil, fmt.Errorf("member: redis is required")
	}

	cfg := param.Config.Defaults()
	otpStore := repository.NewRedisOTPChallengeStore(param.Redis)
	rateStore := repository.NewRedisVerifyRateStore(param.Redis)
	stepUpStore := repository.NewRedisStepUpStore(param.Redis)

	members := param.Members
	tenants := param.Tenants
	identities := param.Identities
	uidGen := param.UIDGen
	totpProfile := param.TOTPProfile

	if param.MongoConf != nil && param.MongoConf.Host != "" {
		if members == nil {
			members = repository.NewMemberRepository(repository.MemberRepositoryParam{Conf: param.MongoConf})
		}
		if tenants == nil {
			tenants = repository.NewTenantRepository(repository.TenantRepositoryParam{Conf: param.MongoConf})
		}
		if identities == nil {
			identities = repository.NewIdentityRepository(repository.IdentityRepositoryParam{Conf: param.MongoConf})
		}
		if totpProfile == nil {
			totpProfile = repository.NewMongoTOTPProfileRepository(param.MongoConf)
		}
	}
	if uidGen == nil {
		uidGen = repository.NewRedisUIDGenerator(param.Redis)
	}
	if totpProfile == nil {
		totpProfile = repository.NewMemoryTOTPProfileRepository()
	}

	mod := &Module{
		OTP:        MustOTPUseCase(OTPUseCaseParam{Store: otpStore, Config: cfg}),
		VerifyRate: MustVerifyRateUseCase(VerifyRateUseCaseParam{Store: rateStore}),
		StepUp:     MustStepUpUseCase(StepUpUseCaseParam{Store: stepUpStore, Config: cfg}),
		Members:    members,
		Tenants:    tenants,
		Identities: identities,
	}

	if members != nil {
		mod.Profile = MustProfileUseCase(ProfileUseCaseParam{Members: members})
	}
	if members != nil && tenants != nil && uidGen != nil {
		mod.Lifecycle = MustLifecycleUseCase(LifecycleUseCaseParam{
			Members: members,
			Tenants: tenants,
			UIDGen:  uidGen,
		})
		mod.Tenant = MustTenantUseCase(TenantUseCaseParam{Tenants: tenants})
	}
	if members != nil && identities != nil && tenants != nil && uidGen != nil {
		mod.Provisioning = MustProvisioningUseCase(ProvisioningUseCaseParam{
			Members:    members,
			Identities: identities,
			Tenants:    tenants,
			UIDGen:     uidGen,
		})
	}

	if cfg.TOTP.SecretKEK != "" {
		cipher, err := libcrypto.NewAESGCMFromString(cfg.TOTP.SecretKEK)
		if err != nil {
			return nil, fmt.Errorf("member: totp kek: %w", err)
		}
		mod.TOTP = MustTOTPUseCase(TOTPUseCaseParam{
			Profile: totpProfile,
			Enroll:  repository.NewRedisTOTPEnrollStore(param.Redis),
			Replay:  repository.NewRedisTOTPReplayStore(param.Redis),
			Cipher:  cipher,
			Config:  cfg,
		})
	}

	return mod, nil
}
