package usecase

import (
	"context"
	"errors"
	"time"

	memberconfig "gateway/internal/model/member/config"
	memberdomain "gateway/internal/model/member/domain"
	domrepo "gateway/internal/model/member/domain/repository"
	domusecase "gateway/internal/model/member/domain/usecase"

	"github.com/google/uuid"
)

type stepUpUseCase struct {
	store  domrepo.StepUpStore
	config memberconfig.Config
}

// StepUpUseCaseParam wires StepUpUseCase.
type StepUpUseCaseParam struct {
	Store  domrepo.StepUpStore
	Config memberconfig.Config
}

// MustStepUpUseCase constructs StepUpUseCase.
func MustStepUpUseCase(param StepUpUseCaseParam) domusecase.StepUpUseCase {
	if param.Store == nil {
		panic("member: step-up store is required")
	}
	return &stepUpUseCase{store: param.Store, config: param.Config.Defaults()}
}

func (uc *stepUpUseCase) Issue(ctx context.Context, req *domusecase.IssueStepUpRequest) (*domusecase.StepUpView, error) {
	if req == nil || req.TenantID == "" || req.UID == "" {
		return nil, errb.InputMissingRequired("tenant_id, uid and purpose are required")
	}
	if !req.Purpose.Valid() {
		return nil, errb.InputInvalidFormat("unsupported step-up purpose")
	}
	ttl := req.TTL
	if ttl <= 0 {
		ttl = time.Duration(uc.config.TOTP.StepUpTTLSeconds) * time.Second
	}
	tokenID := uuid.NewString()
	if err := uc.store.Save(ctx, &domrepo.StepUpSession{
		TokenID:  tokenID,
		TenantID: req.TenantID,
		UID:      req.UID,
		Purpose:  req.Purpose.String(),
	}, ttl); err != nil {
		return nil, wrapRepoErr(err, "save step-up session failed")
	}
	return &domusecase.StepUpView{
		StepUpToken: tokenID,
		ExpiresIn:   int(ttl.Seconds()),
	}, nil
}

func (uc *stepUpUseCase) Consume(ctx context.Context, req *domusecase.ConsumeStepUpRequest) error {
	if req == nil || req.TokenID == "" || req.TenantID == "" || req.UID == "" {
		return errb.InputMissingRequired("step_up_token, tenant_id and uid are required")
	}
	if !req.Purpose.Valid() {
		return errb.InputInvalidFormat("unsupported step-up purpose")
	}
	session, err := uc.store.Get(ctx, req.TokenID)
	if err != nil {
		if errors.Is(err, memberdomain.ErrStepUpNotFound) {
			return errb.ResNotFound("step-up session", req.TokenID).WithCause(err)
		}
		return wrapRepoErr(err, "read step-up session failed")
	}
	if session.TenantID != req.TenantID || session.UID != req.UID || session.Purpose != req.Purpose.String() {
		return errb.AuthForbidden("step-up token mismatch")
	}
	if err := uc.store.Delete(ctx, req.TokenID); err != nil {
		return wrapRepoErr(err, "delete step-up session failed")
	}
	return nil
}

var _ domusecase.StepUpUseCase = (*stepUpUseCase)(nil)
