package repository

import (
	"context"

	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	"haixun-backend/internal/model/mention_inbox/domain/entity"
	domrepo "haixun-backend/internal/model/mention_inbox/domain/repository"

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

type mongoRepository struct {
	collection *mongo.Collection
}

func NewMongoRepository(db *mongo.Database) domrepo.Repository {
	if db == nil {
		return &mongoRepository{}
	}
	return &mongoRepository{collection: db.Collection(entity.CollectionName)}
}

func (r *mongoRepository) EnsureIndexes(ctx context.Context) error {
	if r.collection == nil {
		return nil
	}
	_, err := r.collection.Indexes().CreateMany(ctx, []mongo.IndexModel{
		{
			Keys: bson.D{
				{Key: "tenant_id", Value: 1},
				{Key: "owner_uid", Value: 1},
				{Key: "account_id", Value: 1},
				{Key: "media_id", Value: 1},
			},
			Options: options.Index().SetUnique(true),
		},
		{
			Keys: bson.D{
				{Key: "tenant_id", Value: 1},
				{Key: "owner_uid", Value: 1},
				{Key: "account_id", Value: 1},
				{Key: "synced_at", Value: -1},
			},
		},
		{
			Keys: bson.D{
				{Key: "tenant_id", Value: 1},
				{Key: "owner_uid", Value: 1},
				{Key: "account_id", Value: 1},
				{Key: "timestamp", Value: -1},
			},
		},
	})
	return err
}

func (r *mongoRepository) Upsert(ctx context.Context, item *entity.Mention) (*entity.Mention, error) {
	if r.collection == nil {
		return nil, app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured")
	}
	if item == nil {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("mention is required")
	}
	filter := actorMediaFilter(item.TenantID, item.OwnerUID, item.AccountID, item.MediaID)
	opts := options.Replace().SetUpsert(true)
	if _, err := r.collection.ReplaceOne(ctx, filter, item, opts); err != nil {
		return nil, err
	}
	return item, nil
}

func (r *mongoRepository) Get(ctx context.Context, tenantID, ownerUID, accountID, mediaID string) (*entity.Mention, error) {
	if r.collection == nil {
		return nil, app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured")
	}
	var out entity.Mention
	err := r.collection.FindOne(ctx, actorMediaFilter(tenantID, ownerUID, accountID, mediaID)).Decode(&out)
	if err != nil {
		if err == mongo.ErrNoDocuments {
			return nil, app.For(code.ThreadsAccount).ResNotFound("mention inbox item not found")
		}
		return nil, err
	}
	return &out, nil
}

func normalizeMentionPage(page, pageSize int) (int, int) {
	if page <= 0 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = 10
	}
	if pageSize > 50 {
		pageSize = 50
	}
	return page, pageSize
}

func accountFilter(tenantID, ownerUID, accountID string) bson.M {
	return bson.M{
		"tenant_id":  tenantID,
		"owner_uid":  ownerUID,
		"account_id": accountID,
	}
}

func (r *mongoRepository) CountByAccount(ctx context.Context, tenantID, ownerUID, accountID string) (int64, error) {
	if r.collection == nil {
		return 0, app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured")
	}
	return r.collection.CountDocuments(ctx, accountFilter(tenantID, ownerUID, accountID))
}

func (r *mongoRepository) CountReplyReadyByAccount(ctx context.Context, tenantID, ownerUID, accountID string) (int64, error) {
	if r.collection == nil {
		return 0, app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured")
	}
	filter := accountFilter(tenantID, ownerUID, accountID)
	filter["reply_ready"] = true
	return r.collection.CountDocuments(ctx, filter)
}

func (r *mongoRepository) ListByAccount(ctx context.Context, tenantID, ownerUID, accountID string, page, pageSize int) (*domrepo.ListResult, error) {
	if r.collection == nil {
		return nil, app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured")
	}
	page, pageSize = normalizeMentionPage(page, pageSize)
	filter := accountFilter(tenantID, ownerUID, accountID)
	total, err := r.collection.CountDocuments(ctx, filter)
	if err != nil {
		return nil, err
	}
	sort := bson.D{{Key: "timestamp", Value: -1}, {Key: "synced_at", Value: -1}}
	opts := options.Find().
		SetSort(sort).
		SetSkip(int64((page - 1) * pageSize)).
		SetLimit(int64(pageSize))
	cur, err := r.collection.Find(ctx, filter, opts)
	if err != nil {
		return nil, err
	}
	defer cur.Close(ctx)
	out := make([]entity.Mention, 0, pageSize)
	for cur.Next(ctx) {
		var item entity.Mention
		if err := cur.Decode(&item); err != nil {
			return nil, err
		}
		out = append(out, item)
	}
	if err := cur.Err(); err != nil {
		return nil, err
	}
	var newest entity.Mention
	newestErr := r.collection.FindOne(ctx, filter, options.FindOne().SetSort(bson.D{{Key: "synced_at", Value: -1}})).Decode(&newest)
	newestSyncedAt := int64(0)
	if newestErr == nil {
		newestSyncedAt = newest.SyncedAt
	}
	readyTotal, err := r.CountReplyReadyByAccount(ctx, tenantID, ownerUID, accountID)
	if err != nil {
		return nil, err
	}
	return &domrepo.ListResult{
		Items:          out,
		Total:          total,
		ReadyTotal:     readyTotal,
		NewestSyncedAt: newestSyncedAt,
	}, nil
}

func actorMediaFilter(tenantID, ownerUID, accountID, mediaID string) bson.M {
	return bson.M{
		"tenant_id":  tenantID,
		"owner_uid":  ownerUID,
		"account_id": accountID,
		"media_id":   mediaID,
	}
}
