package repository

import (
	"context"
	"time"

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

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

const (
	colAccounts = "threads_accounts"
	colStates   = "threads_oauth_states"
)

// MonStore uses go-zero mon (no entity cache for tokens).
type MonStore struct {
	accounts *mon.Model
	states   *mon.Model
}

func NewMonStore(uri, database string) *MonStore {
	uri = libmongo.MustMongoURI(uri)
	return &MonStore{
		accounts: mon.MustNewModel(uri, database, colAccounts),
		states:   mon.MustNewModel(uri, database, colStates),
	}
}

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

func (s *MonStore) Update(ctx context.Context, a *domain.Account) error {
	a.UpdatedAt = domain.NowNano()
	res, err := s.accounts.ReplaceOne(ctx, bson.M{"_id": a.ID}, a)
	if err != nil {
		return err
	}
	if res.MatchedCount == 0 {
		return domain.ErrNotFound
	}
	return nil
}

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

func (s *MonStore) FindByOwnerAndThreadsUser(ctx context.Context, ownerUID int64, threadsUserID string) (*domain.Account, error) {
	if threadsUserID == "" {
		return nil, domain.ErrNotFound
	}
	var a domain.Account
	err := s.accounts.FindOne(ctx, &a, bson.M{"owner_uid": ownerUID, "threads_user_id": threadsUserID})
	if err != nil {
		if err == mon.ErrNotFound {
			return nil, domain.ErrNotFound
		}
		return nil, err
	}
	return &a, nil
}

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

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

func (s *MonStore) SaveState(ctx context.Context, st *domain.OAuthState) error {
	_, err := s.states.InsertOne(ctx, st)
	return err
}

func (s *MonStore) TakeState(ctx context.Context, state string) (*domain.OAuthState, error) {
	var st domain.OAuthState
	err := s.states.FindOneAndDelete(ctx, &st, bson.M{"_id": state})
	if err != nil {
		if err == mon.ErrNotFound {
			return nil, domain.ErrInvalidState
		}
		return nil, err
	}
	if st.ExpiresAt > 0 && time.Now().UTC().UnixNano() > st.ExpiresAt {
		return nil, domain.ErrInvalidState
	}
	return &st, nil
}

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