package usecase

import (
	"context"
	"errors"
	"time"

	member "gateway/internal/model/member/domain"
	"gateway/internal/model/member/domain/entity"
	"gateway/internal/model/member/domain/enum"
	domrepo "gateway/internal/model/member/domain/repository"
	domusecase "gateway/internal/model/member/domain/usecase"
)

type lifecycleUseCase struct {
	members domrepo.MemberRepository
	tenants domrepo.TenantRepository
	uidGen  domrepo.UIDGenerator
}

// LifecycleUseCaseParam wires LifecycleUseCase.
type LifecycleUseCaseParam struct {
	Members domrepo.MemberRepository
	Tenants domrepo.TenantRepository
	UIDGen  domrepo.UIDGenerator
}

// MustLifecycleUseCase constructs LifecycleUseCase.
func MustLifecycleUseCase(param LifecycleUseCaseParam) domusecase.LifecycleUseCase {
	if param.Members == nil || param.Tenants == nil || param.UIDGen == nil {
		panic("member: lifecycle dependencies are required")
	}
	return &lifecycleUseCase{
		members: param.Members,
		tenants: param.Tenants,
		uidGen:  param.UIDGen,
	}
}

func (uc *lifecycleUseCase) CreateUnverified(ctx context.Context, req *domusecase.CreatePlatformMemberRequest) (*domusecase.MemberDTO, error) {
	if req == nil || req.TenantID == "" || req.Email == "" {
		return nil, errb.InputMissingRequired("tenant_id and email are required")
	}
	tenant, err := uc.tenants.GetByTenantID(ctx, req.TenantID)
	if err != nil {
		if errors.Is(err, member.ErrTenantNotFound) {
			return nil, errb.ResNotFound("tenant", req.TenantID).WithCause(err)
		}
		return nil, wrapRepoErr(err, "read tenant failed")
	}
	uid, err := uc.uidGen.Next(ctx, req.TenantID, tenant.UIDPrefix)
	if err != nil {
		return nil, wrapRepoErr(err, "allocate uid failed")
	}
	now := time.Now().UTC().UnixMilli()
	rec := &entity.Member{
		TenantID:      req.TenantID,
		UID:           uid,
		ZitadelUserID: req.ZitadelUserID,
		ZitadelEmail:  req.Email,
		DisplayName:   req.DisplayName,
		Language:      defaultLanguage(req.Language),
		Status:        enum.MemberStatusUnverified,
		Origin:        enum.MemberOriginPlatformNative,
		PasswordHash:  req.PasswordHash,
		CreateAt:      now,
		UpdateAt:      now,
	}
	if err := uc.members.Insert(ctx, rec); err != nil {
		if errors.Is(err, member.ErrDuplicateMember) {
			return nil, errb.ResAlreadyExist("member already exists").WithCause(err)
		}
		return nil, wrapRepoErr(err, "create member failed")
	}
	return memberToDTO(rec), nil
}

func (uc *lifecycleUseCase) Activate(ctx context.Context, tenantID, uid string) error {
	return uc.transition(ctx, tenantID, uid, enum.MemberStatusUnverified, enum.MemberStatusActive, "")
}

func (uc *lifecycleUseCase) Suspend(ctx context.Context, tenantID, uid, reason string) error {
	return uc.transition(ctx, tenantID, uid, enum.MemberStatusActive, enum.MemberStatusSuspended, reason)
}

func (uc *lifecycleUseCase) Reactivate(ctx context.Context, tenantID, uid string) error {
	return uc.transition(ctx, tenantID, uid, enum.MemberStatusSuspended, enum.MemberStatusActive, "")
}

func (uc *lifecycleUseCase) SoftDelete(ctx context.Context, tenantID, uid string) error {
	rec, err := uc.members.GetByUID(ctx, tenantID, uid)
	if err != nil {
		if errors.Is(err, member.ErrNotFound) {
			return errb.ResNotFound("member", uid).WithCause(err)
		}
		return wrapRepoErr(err, "read member failed")
	}
	if rec.Status == enum.MemberStatusDeleted {
		return nil
	}
	if err := uc.members.UpdateStatus(ctx, tenantID, uid, enum.MemberStatusDeleted, ""); err != nil {
		return wrapRepoErr(err, "soft delete member failed")
	}
	return nil
}

func (uc *lifecycleUseCase) AbortPending(ctx context.Context, tenantID, uid string) error {
	return uc.transition(ctx, tenantID, uid, enum.MemberStatusUnverified, enum.MemberStatusDeleted, "")
}

func (uc *lifecycleUseCase) transition(ctx context.Context, tenantID, uid string, from, to enum.MemberStatus, reason string) error {
	if tenantID == "" || uid == "" {
		return errb.InputMissingRequired("tenant_id and uid are required")
	}
	rec, err := uc.members.GetByUID(ctx, tenantID, uid)
	if err != nil {
		if errors.Is(err, member.ErrNotFound) {
			return errb.ResNotFound("member", uid).WithCause(err)
		}
		return wrapRepoErr(err, "read member failed")
	}
	if rec.Status != from {
		return errb.ResInvalidState("invalid member status transition").WithCause(member.ErrInvalidStatus)
	}
	if err := uc.members.UpdateStatus(ctx, tenantID, uid, to, reason); err != nil {
		return wrapRepoErr(err, "update member status failed")
	}
	return nil
}
