package repository

import (
	"context"

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

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

const col = "notifications"

type MonStore struct {
	n *mon.Model
}

func NewMonStore(uri, database string) *MonStore {
	uri = libmongo.MustMongoURI(uri)
	return &MonStore{n: mon.MustNewModel(uri, database, col)}
}

func (s *MonStore) Insert(ctx context.Context, n *domain.Notification) error {
	_, err := s.n.InsertOne(ctx, n)
	return err
}

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

func (s *MonStore) UnreadCount(ctx context.Context, ownerUID int64) (int64, error) {
	var list2 []*domain.Notification
	if err := s.n.Find(ctx, &list2, bson.M{"owner_uid": ownerUID}); err != nil {
		return 0, err
	}
	var c int64
	for _, n := range list2 {
		if n.ReadAt == 0 {
			c++
		}
	}
	return c, nil
}

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

func (s *MonStore) MarkRead(ctx context.Context, ownerUID int64, id string) error {
	n, err := s.FindByID(ctx, id)
	if err != nil {
		return err
	}
	if n.OwnerUID != ownerUID {
		return domain.ErrForbidden
	}
	if n.ReadAt == 0 {
		n.ReadAt = domain.NowNano()
		_, err = s.n.ReplaceOne(ctx, bson.M{"_id": id}, n)
	}
	return err
}

func (s *MonStore) MarkAllRead(ctx context.Context, ownerUID int64) error {
	list, err := s.ListByOwner(ctx, ownerUID)
	if err != nil {
		return err
	}
	now := domain.NowNano()
	for _, n := range list {
		if n.ReadAt == 0 {
			n.ReadAt = now
			_, _ = s.n.ReplaceOne(ctx, bson.M{"_id": n.ID}, n)
		}
	}
	return nil
}

func (s *MonStore) FindLatestByJobRef(ctx context.Context, ownerUID int64, jobID string) (*domain.Notification, error) {
	if jobID == "" {
		return nil, domain.ErrNotFound
	}
	var list []*domain.Notification
	err := s.n.Find(ctx, &list, bson.M{
		"owner_uid": ownerUID,
		"kind":      domain.KindJob,
		"ref_type":  domain.RefJob,
		"ref_id":    jobID,
	}, options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}}).SetLimit(1))
	if err != nil {
		return nil, err
	}
	if len(list) == 0 {
		return nil, domain.ErrNotFound
	}
	return list[0], nil
}

func (s *MonStore) Replace(ctx context.Context, n *domain.Notification) error {
	if n == nil || n.ID == "" {
		return domain.ErrNotFound
	}
	res, err := s.n.ReplaceOne(ctx, bson.M{"_id": n.ID}, n)
	if err != nil {
		return err
	}
	if res.MatchedCount == 0 {
		return domain.ErrNotFound
	}
	return nil
}

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