package repository

import (
	"context"
	"errors"
	"strings"
	"time"

	libmongo "gateway/internal/library/mongo"
	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"

	"go.mongodb.org/mongo-driver/v2/bson"
	mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

// MemberRepositoryParam configures the Mongo member repository.
type MemberRepositoryParam struct {
	Conf *libmongo.Conf
}

type memberRepository struct {
	db libmongo.DocumentDBUseCase
}

// NewMemberRepository creates a Mongo-backed MemberRepository.
func NewMemberRepository(param MemberRepositoryParam) domrepo.MemberRepository {
	documentDB, err := libmongo.NewDocumentDB(param.Conf, entity.Member{}.CollectionName())
	if err != nil {
		panic(err)
	}
	return &memberRepository{db: documentDB}
}

func (r *memberRepository) Insert(ctx context.Context, rec *entity.Member) error {
	now := time.Now().UTC().UnixMilli()
	if rec.ID.IsZero() {
		rec.ID = bson.NewObjectID()
	}
	if rec.CreateAt == 0 {
		rec.CreateAt = now
	}
	if rec.UpdateAt == 0 {
		rec.UpdateAt = now
	}
	_, err := r.db.GetClient().InsertOne(ctx, rec)
	if err != nil {
		if mongodriver.IsDuplicateKeyError(err) {
			return member.ErrDuplicateMember
		}
		return err
	}
	return nil
}

func (r *memberRepository) GetByUID(ctx context.Context, tenantID, uid string) (*entity.Member, error) {
	var doc entity.Member
	filter := bson.M{
		member.BSONFieldTenantID: tenantID,
		member.BSONFieldUID:      uid,
	}
	if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, member.ErrNotFound
		}
		return nil, err
	}
	return &doc, nil
}

func (r *memberRepository) GetByZitadelUserID(ctx context.Context, tenantID, zitadelUserID string) (*entity.Member, error) {
	var doc entity.Member
	filter := bson.M{
		member.BSONFieldTenantID:      tenantID,
		member.BSONFieldZitadelUserID: zitadelUserID,
	}
	if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, member.ErrNotFound
		}
		return nil, err
	}
	return &doc, nil
}

func (r *memberRepository) GetByZitadelEmail(ctx context.Context, tenantID, email string) (*entity.Member, error) {
	var doc entity.Member
	filter := bson.M{
		member.BSONFieldTenantID:     tenantID,
		member.BSONFieldZitadelEmail: strings.ToLower(strings.TrimSpace(email)),
	}
	if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, member.ErrNotFound
		}
		return nil, err
	}
	return &doc, nil
}

func (r *memberRepository) UpdateProfile(ctx context.Context, tenantID, uid string, update *domrepo.MemberUpdate) (*entity.Member, error) {
	set := bson.M{member.BSONFieldUpdateAt: time.Now().UTC().UnixMilli()}
	if update.DisplayName != nil {
		set[member.BSONFieldDisplayName] = *update.DisplayName
	}
	if update.Avatar != nil {
		set[member.BSONFieldAvatar] = *update.Avatar
	}
	if update.Language != nil {
		set[member.BSONFieldLanguage] = *update.Language
	}
	if update.Currency != nil {
		set[member.BSONFieldCurrency] = *update.Currency
	}
	if update.Phone != nil {
		set[member.BSONFieldPhone] = *update.Phone
	}
	filter := bson.M{member.BSONFieldTenantID: tenantID, member.BSONFieldUID: uid}
	opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
	var doc entity.Member
	if err := r.db.GetClient().FindOneAndUpdate(ctx, &doc, filter, bson.M{bsonOpSet: set}, opts); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, member.ErrNotFound
		}
		return nil, err
	}
	return &doc, nil
}

func (r *memberRepository) UpdateStatus(ctx context.Context, tenantID, uid string, status enum.MemberStatus, suspendReason string) error {
	set := bson.M{
		member.BSONFieldMemberStatus: status,
		member.BSONFieldUpdateAt:     time.Now().UTC().UnixMilli(),
	}
	if status == enum.MemberStatusSuspended {
		set[member.BSONFieldSuspendReason] = suspendReason
	}
	if status == enum.MemberStatusDeleted {
		set[member.BSONFieldDeletedAt] = time.Now().UTC().UnixMilli()
	}
	filter := bson.M{member.BSONFieldTenantID: tenantID, member.BSONFieldUID: uid}
	res, err := r.db.GetClient().UpdateOne(ctx, filter, bson.M{bsonOpSet: set})
	if err != nil {
		return err
	}
	if res.MatchedCount == 0 {
		return member.ErrNotFound
	}
	return nil
}

func (r *memberRepository) List(ctx context.Context, filter domrepo.ListMembersFilter) ([]*entity.Member, int64, error) {
	q := bson.M{member.BSONFieldTenantID: filter.TenantID}
	if filter.Status != "" {
		q[member.BSONFieldMemberStatus] = filter.Status
	}
	total, err := r.db.GetClient().CountDocuments(ctx, q)
	if err != nil {
		return nil, 0, err
	}
	limit := filter.Limit
	if limit <= 0 {
		limit = 20
	}
	if limit > 100 {
		limit = 100
	}
	opts := options.Find().
		SetSkip(filter.Offset).
		SetLimit(limit).
		SetSort(bson.D{{Key: member.BSONFieldCreateAt, Value: -1}})
	var docs []*entity.Member
	if err := r.db.GetClient().Find(ctx, &docs, q, opts); err != nil {
		return nil, 0, err
	}
	return docs, total, nil
}

func (r *memberRepository) SetBusinessEmailVerified(ctx context.Context, tenantID, uid, email string) error {
	now := time.Now().UTC().UnixMilli()
	set := bson.M{
		member.BSONFieldBusinessEmail:           email,
		member.BSONFieldBusinessEmailVerified:   true,
		member.BSONFieldBusinessEmailVerifiedAt: now,
		member.BSONFieldUpdateAt:                now,
	}
	filter := bson.M{member.BSONFieldTenantID: tenantID, member.BSONFieldUID: uid}
	res, err := r.db.GetClient().UpdateOne(ctx, filter, bson.M{bsonOpSet: set})
	if err != nil {
		return err
	}
	if res.MatchedCount == 0 {
		return member.ErrNotFound
	}
	return nil
}

func (r *memberRepository) SetBusinessPhoneVerified(ctx context.Context, tenantID, uid, phone string) error {
	now := time.Now().UTC().UnixMilli()
	set := bson.M{
		member.BSONFieldBusinessPhone:           phone,
		member.BSONFieldBusinessPhoneVerified:   true,
		member.BSONFieldBusinessPhoneVerifiedAt: now,
		member.BSONFieldUpdateAt:                now,
	}
	filter := bson.M{member.BSONFieldTenantID: tenantID, member.BSONFieldUID: uid}
	res, err := r.db.GetClient().UpdateOne(ctx, filter, bson.M{bsonOpSet: set})
	if err != nil {
		return err
	}
	if res.MatchedCount == 0 {
		return member.ErrNotFound
	}
	return nil
}

// Index20260520001UP ensures members collection indexes exist.
func (r *memberRepository) Index20260520001UP(ctx context.Context) error {
	if err := r.db.PopulateMultiIndex(ctx,
		[]string{member.BSONFieldTenantID, member.BSONFieldUID},
		[]int32{1, 1}, true); err != nil {
		return err
	}
	if err := r.db.PopulateMultiIndex(ctx,
		[]string{member.BSONFieldTenantID, member.BSONFieldZitadelUserID},
		[]int32{1, 1}, true); err != nil {
		return err
	}
	if err := r.db.PopulateMultiIndex(ctx,
		[]string{member.BSONFieldTenantID, member.BSONFieldZitadelEmail},
		[]int32{1, 1}, false); err != nil {
		return err
	}
	return r.db.PopulateMultiIndex(ctx,
		[]string{member.BSONFieldTenantID, member.BSONFieldMemberStatus, member.BSONFieldCreateAt},
		[]int32{1, 1, -1}, false)
}

var _ domrepo.MemberRepository = (*memberRepository)(nil)
