package repository

import (
	"context"

	libmongo "apps/backend/internal/lib/mongo"
	"apps/backend/internal/module/growth/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 {
	outcomes   *mon.Model
	checkups   *mon.Model
	health     *mon.Model
	workspaces *mon.Model
	wsState    *mon.Model
	reviews    *mon.Model
	rewards    *mon.Model
	playbooks  *mon.Model
	utm        *mon.Model
	wsMembers  *mon.Model
	bench      *mon.Model
}

func NewMonStore(uri, database string) *MonStore {
	uri = libmongo.MustMongoURI(uri)
	return &MonStore{
		outcomes:   mon.MustNewModel(uri, database, "growth_outcomes"),
		checkups:   mon.MustNewModel(uri, database, "growth_checkups"),
		health:     mon.MustNewModel(uri, database, "growth_account_health"),
		workspaces: mon.MustNewModel(uri, database, "growth_workspaces"),
		wsState:    mon.MustNewModel(uri, database, "growth_ws_state"),
		reviews:    mon.MustNewModel(uri, database, "growth_draft_reviews"),
		rewards:    mon.MustNewModel(uri, database, "growth_invite_rewards"),
		playbooks:  mon.MustNewModel(uri, database, "growth_playbooks"),
		utm:        mon.MustNewModel(uri, database, "growth_utm_links"),
		wsMembers:  mon.MustNewModel(uri, database, "growth_ws_members"),
		bench:      mon.MustNewModel(uri, database, "growth_benchmark"),
	}
}

func (s *MonStore) UpsertOutcomeBySource(ctx context.Context, e *domain.OutcomeEvent) error {
	filter := bson.M{"owner_uid": e.OwnerUID, "source_type": e.SourceType, "source_id": e.SourceID}
	var existing domain.OutcomeEvent
	err := s.outcomes.FindOne(ctx, &existing, filter)
	if err == nil {
		// 發佈 hook 可能重送；已存在就保持原 sent_at 與所有觀測／成交訊號。
		*e = existing
		return nil
	}
	if err != mon.ErrNotFound {
		return err
	}
	if e.ID == "" {
		e.ID = domain.NewID()
	}
	_, err = s.outcomes.InsertOne(ctx, e)
	return err
}

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

func (s *MonStore) ListOutcomes(ctx context.Context, ownerUID int64, f domain.OutcomeListFilter) ([]*domain.OutcomeEvent, int64, error) {
	q := bson.M{"owner_uid": ownerUID}
	if f.Kind != "" {
		q["kind"] = f.Kind
	}
	if f.Confidence != "" {
		q["confidence"] = f.Confidence
	}
	if f.SourceType != "" {
		q["source_type"] = f.SourceType
	}
	if f.From > 0 || f.To > 0 {
		rangeQ := bson.M{}
		if f.From > 0 {
			rangeQ["$gte"] = f.From
		}
		if f.To > 0 {
			rangeQ["$lte"] = f.To
		}
		q["sent_at"] = rangeQ
	}
	total, err := s.outcomes.CountDocuments(ctx, q)
	if err != nil {
		return nil, 0, err
	}
	page, ps := f.Page, f.PageSize
	if page < 1 {
		page = 1
	}
	if ps < 1 {
		ps = 20
	}
	var list []*domain.OutcomeEvent
	err = s.outcomes.Find(ctx, &list, q, options.Find().
		SetSort(bson.D{{Key: "sent_at", Value: -1}}).
		SetSkip(int64((page-1)*ps)).
		SetLimit(int64(ps)))
	return list, total, err
}

func (s *MonStore) SaveOutcome(ctx context.Context, e *domain.OutcomeEvent) error {
	_, err := s.outcomes.UpdateOne(ctx, bson.M{"_id": e.ID}, bson.M{"$set": e}, options.Update().SetUpsert(true))
	return err
}

func (s *MonStore) ListObservingDue(ctx context.Context, now int64, limit int) ([]*domain.OutcomeEvent, error) {
	var list []*domain.OutcomeEvent
	opts := options.Find().SetSort(bson.D{{Key: "sent_at", Value: 1}})
	if limit > 0 {
		opts.SetLimit(int64(limit))
	}
	err := s.outcomes.Find(ctx, &list, bson.M{"status": domain.StatusObserving}, opts)
	return list, err
}

func (s *MonStore) SaveCheckup(ctx context.Context, c *domain.WeeklyCheckup) error {
	_, err := s.checkups.UpdateOne(ctx, bson.M{"_id": c.ID}, bson.M{"$set": c}, options.Update().SetUpsert(true))
	return err
}

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

func (s *MonStore) LatestCheckup(ctx context.Context, ownerUID int64) (*domain.WeeklyCheckup, error) {
	var list []*domain.WeeklyCheckup
	err := s.checkups.Find(ctx, &list, bson.M{"owner_uid": ownerUID},
		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) ListCheckups(ctx context.Context, ownerUID int64, page, pageSize int) ([]*domain.WeeklyCheckup, int64, error) {
	q := bson.M{"owner_uid": ownerUID}
	total, err := s.checkups.CountDocuments(ctx, q)
	if err != nil {
		return nil, 0, err
	}
	if page < 1 {
		page = 1
	}
	if pageSize < 1 {
		pageSize = 20
	}
	var list []*domain.WeeklyCheckup
	err = s.checkups.Find(ctx, &list, q, options.Find().
		SetSort(bson.D{{Key: "created_at", Value: -1}}).
		SetSkip(int64((page-1)*pageSize)).SetLimit(int64(pageSize)))
	return list, total, err
}

func (s *MonStore) CountCheckupGenerations(ctx context.Context, ownerUID int64, sinceNano int64) (int64, error) {
	return s.checkups.CountDocuments(ctx, bson.M{"owner_uid": ownerUID, "created_at": bson.M{"$gte": sinceNano}})
}

func (s *MonStore) SaveHealth(ctx context.Context, h *domain.AccountHealth) error {
	_, err := s.health.UpdateOne(ctx, bson.M{"_id": h.ThreadsAccountID}, bson.M{"$set": h}, options.Update().SetUpsert(true))
	return err
}

func (s *MonStore) GetHealth(ctx context.Context, accountID string) (*domain.AccountHealth, error) {
	var h domain.AccountHealth
	err := s.health.FindOne(ctx, &h, bson.M{"_id": accountID})
	if err == mon.ErrNotFound {
		return nil, domain.ErrNotFound
	}
	return &h, err
}

func (s *MonStore) ListHealth(ctx context.Context, ownerUID int64) ([]*domain.AccountHealth, error) {
	var list []*domain.AccountHealth
	err := s.health.Find(ctx, &list, bson.M{"owner_uid": ownerUID})
	return list, err
}

func (s *MonStore) SaveWorkspace(ctx context.Context, w *domain.Workspace) error {
	_, err := s.workspaces.UpdateOne(ctx, bson.M{"_id": w.ID}, bson.M{"$set": w}, options.Update().SetUpsert(true))
	return err
}

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

func (s *MonStore) ListWorkspaces(ctx context.Context, ownerUID int64, includeArchived bool) ([]*domain.Workspace, error) {
	q := bson.M{"owner_uid": ownerUID}
	if !includeArchived {
		q["archived"] = bson.M{"$ne": true}
	}
	var list []*domain.Workspace
	err := s.workspaces.Find(ctx, &list, q, options.Find().SetSort(bson.D{{Key: "created_at", Value: 1}}))
	return list, err
}

func (s *MonStore) CountActiveWorkspaces(ctx context.Context, ownerUID int64) (int64, error) {
	return s.workspaces.CountDocuments(ctx, bson.M{"owner_uid": ownerUID, "archived": bson.M{"$ne": true}})
}

func (s *MonStore) GetMemberWSState(ctx context.Context, ownerUID int64) (*domain.MemberWorkspaceState, error) {
	var st domain.MemberWorkspaceState
	err := s.wsState.FindOne(ctx, &st, bson.M{"_id": ownerUID})
	if err == mon.ErrNotFound {
		return nil, domain.ErrNotFound
	}
	return &st, err
}

func (s *MonStore) SaveMemberWSState(ctx context.Context, st *domain.MemberWorkspaceState) error {
	_, err := s.wsState.UpdateOne(ctx, bson.M{"_id": st.OwnerUID}, bson.M{"$set": st}, options.Update().SetUpsert(true))
	return err
}

func (s *MonStore) SaveReview(ctx context.Context, r *domain.DraftReview) error {
	_, err := s.reviews.UpdateOne(ctx, bson.M{"_id": r.ID}, bson.M{"$set": r}, options.Update().SetUpsert(true))
	return err
}

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

func (s *MonStore) FindReviewByRef(ctx context.Context, ownerUID int64, refType, refID string) (*domain.DraftReview, error) {
	var list []*domain.DraftReview
	err := s.reviews.Find(ctx, &list, bson.M{"owner_uid": ownerUID, "ref_type": refType, "ref_id": refID},
		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
	}
	return list[0], nil
}

func (s *MonStore) ListReviews(ctx context.Context, ownerUID int64, status string, page, pageSize int) ([]*domain.DraftReview, int64, error) {
	q := bson.M{"owner_uid": ownerUID}
	if status != "" {
		q["status"] = status
	}
	total, err := s.reviews.CountDocuments(ctx, q)
	if err != nil {
		return nil, 0, err
	}
	if page < 1 {
		page = 1
	}
	if pageSize < 1 {
		pageSize = 20
	}
	var list []*domain.DraftReview
	err = s.reviews.Find(ctx, &list, q, options.Find().SetSkip(int64((page-1)*pageSize)).SetLimit(int64(pageSize)))
	return list, total, err
}

func (s *MonStore) SaveInviteReward(ctx context.Context, r *domain.InviteReward) error {
	_, err := s.rewards.InsertOne(ctx, r)
	return err
}

func (s *MonStore) ListInviteRewards(ctx context.Context, inviterUID int64, limit int) ([]*domain.InviteReward, error) {
	opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}})
	if limit > 0 {
		opts.SetLimit(int64(limit))
	}
	var list []*domain.InviteReward
	err := s.rewards.Find(ctx, &list, bson.M{"inviter_uid": inviterUID}, opts)
	return list, err
}

func (s *MonStore) SumInviteRewardPointsMonth(ctx context.Context, inviterUID int64, monthStartNano int64) (int, error) {
	var list []*domain.InviteReward
	err := s.rewards.Find(ctx, &list, bson.M{
		"inviter_uid": inviterUID,
		"status":      domain.RewardCredited,
		"created_at":  bson.M{"$gte": monthStartNano},
	})
	if err != nil {
		return 0, err
	}
	sum := 0
	for _, r := range list {
		sum += r.Points
	}
	return sum, nil
}

func (s *MonStore) SumInviteRewardPointsTotal(ctx context.Context, inviterUID int64) (int, error) {
	var list []*domain.InviteReward
	err := s.rewards.Find(ctx, &list, bson.M{"inviter_uid": inviterUID, "status": domain.RewardCredited})
	if err != nil {
		return 0, err
	}
	sum := 0
	for _, r := range list {
		sum += r.Points
	}
	return sum, nil
}

func (s *MonStore) HasInviteRewardForInvitee(ctx context.Context, inviteeUID int64) (bool, error) {
	n, err := s.rewards.CountDocuments(ctx, bson.M{
		"invitee_uid": inviteeUID,
		"status":      bson.M{"$in": []string{domain.RewardCredited, domain.RewardCapped}},
	})
	return n > 0, err
}

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

func (s *MonStore) SavePlaybook(ctx context.Context, p *domain.Playbook) error {
	_, err := s.playbooks.UpdateOne(ctx, bson.M{"_id": p.ID}, bson.M{"$set": p}, options.Update().SetUpsert(true))
	return err
}
func (s *MonStore) GetPlaybook(ctx context.Context, id string) (*domain.Playbook, error) {
	var p domain.Playbook
	err := s.playbooks.FindOne(ctx, &p, bson.M{"_id": id})
	if err == mon.ErrNotFound {
		return nil, domain.ErrNotFound
	}
	return &p, err
}
func (s *MonStore) ListPlaybooks(ctx context.Context, kind, niche string, ownerOnlyUID int64, page, pageSize int) ([]*domain.Playbook, int64, error) {
	q := bson.M{}
	if kind != "" {
		q["kind"] = kind
	}
	if niche != "" {
		q["niche"] = bson.M{"$regex": niche, "$options": "i"}
	}
	if ownerOnlyUID > 0 {
		q["owner_uid"] = ownerOnlyUID
	}
	total, err := s.playbooks.CountDocuments(ctx, q)
	if err != nil {
		return nil, 0, err
	}
	if page < 1 {
		page = 1
	}
	if pageSize < 1 {
		pageSize = 20
	}
	var list []*domain.Playbook
	err = s.playbooks.Find(ctx, &list, q, options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}}).SetSkip(int64((page-1)*pageSize)).SetLimit(int64(pageSize)))
	return list, total, err
}
func (s *MonStore) DeletePlaybook(ctx context.Context, id string) error {
	res, err := s.playbooks.DeleteOne(ctx, bson.M{"_id": id})
	if err != nil {
		return err
	}
	if res == 0 {
		return domain.ErrNotFound
	}
	return nil
}
func (s *MonStore) IncPlaybookImport(ctx context.Context, id string) error {
	_, err := s.playbooks.UpdateOne(ctx, bson.M{"_id": id}, bson.M{"$inc": bson.M{"import_count": 1}})
	return err
}
func (s *MonStore) SaveUtmLink(ctx context.Context, u *domain.UtmLink) error {
	_, err := s.utm.UpdateOne(ctx, bson.M{"_id": u.ID}, bson.M{"$set": u}, options.Update().SetUpsert(true))
	return err
}
func (s *MonStore) GetUtmByCode(ctx context.Context, code string) (*domain.UtmLink, error) {
	var u domain.UtmLink
	err := s.utm.FindOne(ctx, &u, bson.M{"code": code})
	if err == mon.ErrNotFound {
		return nil, domain.ErrNotFound
	}
	return &u, err
}
func (s *MonStore) ListUtmLinks(ctx context.Context, ownerUID int64) ([]*domain.UtmLink, error) {
	var list []*domain.UtmLink
	err := s.utm.Find(ctx, &list, bson.M{"owner_uid": ownerUID}, options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}}))
	return list, err
}
func (s *MonStore) IncUtmClick(ctx context.Context, code string) (*domain.UtmLink, error) {
	_, err := s.utm.UpdateOne(ctx, bson.M{"code": code}, bson.M{"$inc": bson.M{"clicks": 1}})
	if err != nil {
		return nil, err
	}
	return s.GetUtmByCode(ctx, code)
}
func (s *MonStore) SaveWorkspaceMember(ctx context.Context, m *domain.WorkspaceMember) error {
	_, err := s.wsMembers.UpdateOne(ctx, bson.M{"_id": m.ID}, bson.M{"$set": m}, options.Update().SetUpsert(true))
	return err
}
func (s *MonStore) ListWorkspaceMembers(ctx context.Context, workspaceID string) ([]*domain.WorkspaceMember, error) {
	var list []*domain.WorkspaceMember
	err := s.wsMembers.Find(ctx, &list, bson.M{"workspace_id": workspaceID})
	return list, err
}
func (s *MonStore) RemoveWorkspaceMember(ctx context.Context, workspaceID string, uid int64) error {
	res, err := s.wsMembers.DeleteOne(ctx, bson.M{"workspace_id": workspaceID, "uid": uid})
	if err != nil {
		return err
	}
	if res == 0 {
		return domain.ErrNotFound
	}
	return nil
}
func (s *MonStore) IsWorkspaceMember(ctx context.Context, workspaceID string, uid int64) (bool, string, error) {
	w, err := s.GetWorkspace(ctx, workspaceID)
	if err == nil && w.OwnerUID == uid {
		return true, domain.WSRoleOwner, nil
	}
	var list []*domain.WorkspaceMember
	err = s.wsMembers.Find(ctx, &list, bson.M{"workspace_id": workspaceID, "uid": uid}, options.Find().SetLimit(1))
	if err != nil {
		return false, "", err
	}
	if len(list) == 0 {
		return false, "", nil
	}
	return true, list[0].Role, nil
}
func (s *MonStore) UpsertBenchmarkSample(ctx context.Context, sample *domain.BenchmarkSample) error {
	if sample.ID == "" {
		sample.ID = sample.Niche + ":" + sample.OwnerHash
	}
	_, err := s.bench.UpdateOne(ctx, bson.M{"_id": sample.ID}, bson.M{"$set": sample}, options.Update().SetUpsert(true))
	return err
}
func (s *MonStore) ListBenchmarkSamples(ctx context.Context, niche string, limit int) ([]*domain.BenchmarkSample, error) {
	q := bson.M{}
	if niche != "" {
		q["niche"] = niche
	}
	opts := options.Find()
	if limit > 0 {
		opts.SetLimit(int64(limit))
	}
	var list []*domain.BenchmarkSample
	err := s.bench.Find(ctx, &list, q, opts)
	return list, err
}
