package repository

import (
	"context"

	libmongo "apps/backend/internal/lib/mongo"
	"apps/backend/internal/module/inspire/domain"

	"github.com/zeromicro/go-zero/core/stores/mon"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type MonStore struct {
	trends   *mon.Model
	elements *mon.Model
	sessions *mon.Model
	active   *mon.Model // inspire_session_active: _id=owner_uid, session_id
}

func NewMonStore(uri, database string) *MonStore {
	uri = libmongo.MustMongoURI(uri)
	return &MonStore{
		trends:   mon.MustNewModel(uri, database, "inspire_trends"),
		elements: mon.MustNewModel(uri, database, "inspire_elements"),
		sessions: mon.MustNewModel(uri, database, "inspire_sessions"),
		active:   mon.MustNewModel(uri, database, "inspire_session_active"),
	}
}

type trendBag struct {
	OwnerUID int64               `bson:"_id"`
	Items    []*domain.TrendItem `bson:"items"`
}

type activeRef struct {
	OwnerUID  int64  `bson:"_id"`
	SessionID string `bson:"session_id"`
}

func (s *MonStore) ListTrends(ctx context.Context, ownerUID int64) ([]*domain.TrendItem, error) {
	var bag trendBag
	err := s.trends.FindOne(ctx, &bag, bson.M{"_id": ownerUID})
	if err != nil {
		if err == mon.ErrNotFound {
			return nil, nil
		}
		return nil, err
	}
	return bag.Items, nil
}

func (s *MonStore) SaveTrends(ctx context.Context, ownerUID int64, items []*domain.TrendItem) error {
	bag := trendBag{OwnerUID: ownerUID, Items: items}
	_, err := s.trends.ReplaceOne(ctx, bson.M{"_id": ownerUID}, bag, options.Replace().SetUpsert(true))
	return err
}

func (s *MonStore) ListElements(ctx context.Context, ownerUID int64) ([]*domain.Element, error) {
	var list []*domain.Element
	err := s.elements.Find(ctx, &list, bson.M{"owner_uid": ownerUID},
		options.Find().SetSort(bson.D{{Key: "updated_at", Value: -1}}))
	return list, err
}

func (s *MonStore) SaveElement(ctx context.Context, e *domain.Element) error {
	_, err := s.elements.ReplaceOne(ctx, bson.M{"_id": e.ID}, e, options.Replace().SetUpsert(true))
	return err
}

func (s *MonStore) DeleteElement(ctx context.Context, id string) error {
	res, err := s.elements.DeleteOne(ctx, bson.M{"_id": id})
	if err != nil {
		return err
	}
	if res == 0 {
		return domain.ErrNotFound
	}
	return nil
}

func (s *MonStore) GetElement(ctx context.Context, id string) (*domain.Element, error) {
	var e domain.Element
	err := s.elements.FindOne(ctx, &e, bson.M{"_id": id})
	if err != nil {
		if err == mon.ErrNotFound {
			return nil, domain.ErrNotFound
		}
		return nil, err
	}
	return &e, nil
}

func (s *MonStore) ListSessions(ctx context.Context, ownerUID int64) ([]*domain.Session, error) {
	var list []*domain.Session
	err := s.sessions.Find(ctx, &list, bson.M{"owner_uid": ownerUID},
		options.Find().SetSort(bson.D{{Key: "updated_at", Value: -1}}).SetLimit(50))
	if err != nil {
		return nil, err
	}
	return list, nil
}

// GetSession — active；無 active／失效則取最近一則；都沒有回 NotFound（usecase 建新）。
func (s *MonStore) GetSession(ctx context.Context, ownerUID int64) (*domain.Session, error) {
	if id, err := s.GetActiveSessionID(ctx, ownerUID); err == nil && id != "" {
		if sess, gerr := s.GetSessionByID(ctx, ownerUID, id); gerr == nil {
			return sess, nil
		}
	}
	// fallback：最近更新
	var list []*domain.Session
	err := s.sessions.Find(ctx, &list, bson.M{"owner_uid": ownerUID},
		options.Find().SetSort(bson.D{{Key: "updated_at", Value: -1}}).SetLimit(1))
	if err != nil {
		return nil, err
	}
	if len(list) == 0 {
		return nil, domain.ErrNotFound
	}
	_ = s.SetActiveSessionID(ctx, ownerUID, list[0].ID)
	return list[0], nil
}

func (s *MonStore) GetSessionByID(ctx context.Context, ownerUID int64, id string) (*domain.Session, error) {
	var sess domain.Session
	err := s.sessions.FindOne(ctx, &sess, bson.M{"_id": id, "owner_uid": ownerUID})
	if err != nil {
		if err == mon.ErrNotFound {
			return nil, domain.ErrNotFound
		}
		return nil, err
	}
	return &sess, nil
}

func (s *MonStore) SaveSession(ctx context.Context, sess *domain.Session) error {
	// 用 _id + owner_uid：多 session 且禁止覆寫別人的對話
	_, err := s.sessions.ReplaceOne(
		ctx,
		bson.M{"_id": sess.ID, "owner_uid": sess.OwnerUID},
		sess,
		options.Replace().SetUpsert(true),
	)
	return err
}

func (s *MonStore) DeleteSessionByID(ctx context.Context, ownerUID int64, id string) error {
	res, err := s.sessions.DeleteOne(ctx, bson.M{"_id": id, "owner_uid": ownerUID})
	if err != nil {
		return err
	}
	if res == 0 {
		return domain.ErrNotFound
	}
	return nil
}

func (s *MonStore) GetActiveSessionID(ctx context.Context, ownerUID int64) (string, error) {
	var ref activeRef
	err := s.active.FindOne(ctx, &ref, bson.M{"_id": ownerUID})
	if err != nil {
		if err == mon.ErrNotFound {
			return "", domain.ErrNotFound
		}
		return "", err
	}
	return ref.SessionID, nil
}

func (s *MonStore) SetActiveSessionID(ctx context.Context, ownerUID int64, sessionID string) error {
	ref := activeRef{OwnerUID: ownerUID, SessionID: sessionID}
	_, err := s.active.ReplaceOne(ctx, bson.M{"_id": ownerUID}, ref, options.Replace().SetUpsert(true))
	return err
}

var _ domain.Repository = (*MonStore)(nil)
