// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package feed

import (
	"context"
	"fmt"
	"strings"

	activities_model "code.gitea.io/gitea/models/activities"
	"code.gitea.io/gitea/models/db"
	access_model "code.gitea.io/gitea/models/perm/access"
	repo_model "code.gitea.io/gitea/models/repo"
	"code.gitea.io/gitea/models/unit"
	user_model "code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/util"
)

func GetFeedsForDashboard(ctx context.Context, opts activities_model.GetFeedsOptions) (activities_model.ActionList, int, error) {
	opts.DontCount = opts.RequestedTeam == nil && opts.Date == ""
	results, cnt, err := activities_model.GetFeeds(ctx, opts)
	return results, util.Iif(opts.DontCount, -1, int(cnt)), err
}

// GetFeeds returns actions according to the provided options
func GetFeeds(ctx context.Context, opts activities_model.GetFeedsOptions) (activities_model.ActionList, int64, error) {
	return activities_model.GetFeeds(ctx, opts)
}

// notifyWatchers creates batch of actions for every watcher.
// It could insert duplicate actions for a repository action, like this:
// * Original action: UserID=1 (the real actor), ActUserID=1
// * Organization action: UserID=100 (the repo's org), ActUserID=1
// * Watcher action: UserID=20 (a user who is watching a repo), ActUserID=1
func notifyWatchers(ctx context.Context, act *activities_model.Action, watchers []*repo_model.Watch, permCode, permIssue, permPR []bool) error {
	// MySQL has TEXT length limit 65535.
	// Sometimes the content is "field1|field2|field3", sometimes the content is JSON (ActionMirrorSyncPush, ActionCommitRepo, ActionPushTag, etc...)
	if left, right := util.EllipsisDisplayStringX(act.Content, 65535); right != "" {
		if strings.HasPrefix(act.Content, `{"`) && strings.HasSuffix(act.Content, `}`) {
			// FIXME: at the moment we can do nothing if the content is JSON and it is too long
			act.Content = "{}"
		} else {
			act.Content = left
		}
	}

	// Add feed for actor.
	act.UserID = act.ActUserID
	if err := db.Insert(ctx, act); err != nil {
		return fmt.Errorf("insert new actioner: %w", err)
	}

	// Add feed for organization
	if act.Repo.Owner.IsOrganization() && act.ActUserID != act.Repo.Owner.ID {
		act.ID = 0
		act.UserID = act.Repo.Owner.ID
		if err := db.Insert(ctx, act); err != nil {
			return fmt.Errorf("insert new actioner: %w", err)
		}
	}

	for i, watcher := range watchers {
		if act.ActUserID == watcher.UserID {
			continue
		}
		act.ID = 0
		act.UserID = watcher.UserID
		act.Repo.Units = nil

		switch act.OpType {
		case activities_model.ActionCommitRepo, activities_model.ActionPushTag, activities_model.ActionDeleteTag, activities_model.ActionPublishRelease, activities_model.ActionDeleteBranch:
			if !permCode[i] {
				continue
			}
		case activities_model.ActionCreateIssue, activities_model.ActionCommentIssue, activities_model.ActionCloseIssue, activities_model.ActionReopenIssue:
			if !permIssue[i] {
				continue
			}
		case activities_model.ActionCreatePullRequest, activities_model.ActionCommentPull, activities_model.ActionMergePullRequest, activities_model.ActionClosePullRequest, activities_model.ActionReopenPullRequest, activities_model.ActionAutoMergePullRequest:
			if !permPR[i] {
				continue
			}
		default:
		}

		if err := db.Insert(ctx, act); err != nil {
			return fmt.Errorf("insert new action: %w", err)
		}
	}

	return nil
}

// NotifyWatchers creates batch of actions for every watcher.
func NotifyWatchers(ctx context.Context, acts ...*activities_model.Action) error {
	return db.WithTx(ctx, func(ctx context.Context) error {
		if len(acts) == 0 {
			return nil
		}

		repoID := acts[0].RepoID
		if repoID == 0 {
			setting.PanicInDevOrTesting("action should belong to a repo")
			return nil
		}
		if err := acts[0].LoadRepo(ctx); err != nil {
			return err
		}
		repo := acts[0].Repo
		if err := repo.LoadOwner(ctx); err != nil {
			return err
		}

		actUserID := acts[0].ActUserID

		// Add feeds for user self and all watchers.
		watchers, err := repo_model.GetWatchers(ctx, repoID)
		if err != nil {
			return fmt.Errorf("get watchers: %w", err)
		}

		permCode := make([]bool, len(watchers))
		permIssue := make([]bool, len(watchers))
		permPR := make([]bool, len(watchers))
		for i, watcher := range watchers {
			user, err := user_model.GetUserByID(ctx, watcher.UserID)
			if err != nil {
				permCode[i] = false
				permIssue[i] = false
				permPR[i] = false
				continue
			}
			perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
			if err != nil {
				permCode[i] = false
				permIssue[i] = false
				permPR[i] = false
				continue
			}
			permCode[i] = perm.CanRead(unit.TypeCode)
			permIssue[i] = perm.CanRead(unit.TypeIssues)
			permPR[i] = perm.CanRead(unit.TypePullRequests)
		}

		for _, act := range acts {
			if act.RepoID != repoID {
				setting.PanicInDevOrTesting("action should belong to the same repo, expected[%d], got[%d] ", repoID, act.RepoID)
			}
			if act.ActUserID != actUserID {
				setting.PanicInDevOrTesting("action should have the same actor, expected[%d], got[%d] ", actUserID, act.ActUserID)
			}

			act.Repo = repo
			if err := notifyWatchers(ctx, act, watchers, permCode, permIssue, permPR); err != nil {
				return err
			}
		}
		return nil
	})
}
