// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT

package f3

import (
	"context"
	"crypto/sha256"
	"fmt"
	"io"
	"strings"
	"testing"
	"time"

	"code.forgejo.org/f3/gof3/v3/f3"
	tests_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository"
	"code.forgejo.org/f3/gof3/v3/path"
	f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
)

func GeneratorSetRandomID(id string, f f3.Interface, parent path.Path) f3.Interface {
	if parent.First().(generic.NodeInterface).GetTree().AllocateID() {
		return f
	}
	return GeneratorSetID(f, id)
}

func GeneratorSetID(f f3.Interface, id string) f3.Interface {
	f.SetID(id)
	return f
}

type ModificatorFunc func(t *testing.T, f f3.Interface, parent path.Path) []f3.Interface

func GeneratorModify(t *testing.T, f f3.Interface, parent path.Path) []f3.Interface {
	switch v := f.(type) {
	case *f3.User:
		return GeneratorModifyUser(v)
	case *f3.Organization:
		return GeneratorModifyOrganization(v)
	case *f3.Project:
		return GeneratorModifyProject(v, parent)
	case *f3.Issue:
		return GeneratorModifyIssue(v, parent)
	case *f3.Milestone:
		return GeneratorModifyMilestone(v, parent)
	case *f3.Topic:
		return GeneratorModifyTopic(v, parent)
	case *f3.Reaction:
		// a reaction cannot be modified, it can only be created and deleted
		return []f3.Interface{}
	case *f3.Label:
		return GeneratorModifyLabel(v, parent)
	case *f3.Comment:
		return GeneratorModifyComment(v, parent)
	case *f3.Release:
		return GeneratorModifyRelease(t, v, parent)
	case *f3.Attachment:
		return GeneratorModifyAttachment(v, parent)
	case *f3.PullRequest:
		return GeneratorModifyPullRequest(t, v, parent)
	case *f3.Review:
		// a review cannot be modified, it can only be created and deleted
		return []f3.Interface{}
	case *f3.ReviewComment:
		return GeneratorModifyReviewComment(t, v, parent)
	default:
		panic(fmt.Errorf("not implemented %T", f))
	}
}

type GeneratorFunc func(t *testing.T, name string, f f3.Interface, parent path.Path) f3.Interface

func GeneratorSetRandom(t *testing.T, name string, f f3.Interface, parent path.Path) f3.Interface {
	GeneratorSetRandomID(name, f, parent)
	switch v := f.(type) {
	case *f3.User:
		return GeneratorSetRandomUser(v)
	case *f3.Organization:
		return GeneratorSetRandomOrganization(v)
	case *f3.Project:
		return GeneratorSetRandomProject(v, parent)
	case *f3.Issue:
		return GeneratorSetRandomIssue(v, parent)
	case *f3.Milestone:
		return GeneratorSetRandomMilestone(v, parent)
	case *f3.Topic:
		return GeneratorSetRandomTopic(v, parent)
	case *f3.Reaction:
		return GeneratorSetRandomReaction(v, parent)
	case *f3.Label:
		return GeneratorSetRandomLabel(v, parent)
	case *f3.Comment:
		return GeneratorSetRandomComment(v, parent)
	case *f3.Release:
		return GeneratorSetRandomRelease(t, v, parent)
	case *f3.Attachment:
		return GeneratorSetRandomAttachment(v, parent)
	case *f3.PullRequest:
		return GeneratorSetRandomPullRequest(t, v, parent)
	case *f3.Review:
		return GeneratorSetReview(t, v, parent)
	case *f3.ReviewComment:
		return GeneratorSetReviewComment(t, v, parent)
	default:
		panic(fmt.Errorf("not implemented %T", f))
	}
}

func GeneratorSetRandomUser(user *f3.User) *f3.User {
	username := fmt.Sprintf("generateduser%s", user.GetID())
	user.Name = username + " Doe"
	user.UserName = username
	user.Email = username + "@example.com"
	user.Password = "Wrobyak4"
	return user
}

func GeneratorModifyUser(user *f3.User) []f3.Interface {
	return []f3.Interface{user.Clone()}
}

func GeneratorSetRandomOrganization(organization *f3.Organization) *f3.Organization {
	organizationname := fmt.Sprintf("generatedorg%s", organization.GetID())
	organization.FullName = organizationname + " Lambda"
	organization.Name = organizationname
	return organization
}

func GeneratorModifyOrganization(organization *f3.Organization) []f3.Interface {
	organization0 := organization.Clone().(*f3.Organization)
	organization0.FullName = "modified " + organization.FullName
	return []f3.Interface{organization0}
}

func GeneratorSetRandomProject(project *f3.Project, parent path.Path) *f3.Project {
	projectname := fmt.Sprintf("project%s", project.GetID())
	project.Name = projectname
	project.IsPrivate = false
	project.IsMirror = false
	project.Description = "project description"
	project.DefaultBranch = "main"
	return project
}

func GeneratorModifyProject(project *f3.Project, parent path.Path) []f3.Interface {
	project0 := project.Clone().(*f3.Project)
	project0.Description = "modified " + project.Description
	return []f3.Interface{project0}
}

func GeneratorSetRandomIssue(issue *f3.Issue, parent path.Path) *f3.Issue {
	user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
	projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
	labelsNode := projectNode.Find(generic.NewPathFromString("labels"))
	labels := labelsNode.GetChildren()
	firstLabel := labels[0]

	now := now()
	updated := tick(&now)
	closed := tick(&now)
	created := tick(&now)

	userRef := f3_tree.NewUserReference(user.GetID())
	labelRef := f3_tree.NewIssueLabelReference(firstLabel.GetID())
	milestonesNode := projectNode.Find(generic.NewPathFromString("milestones"))
	milestones := milestonesNode.GetChildren()
	firstMilestone := milestones[0]

	issue.PosterID = userRef
	issue.Assignees = []*f3.Reference{userRef}
	issue.Labels = []*f3.Reference{labelRef}
	issue.Milestone = f3_tree.NewIssueMilestoneReference(firstMilestone.GetID())
	issue.Title = "title"
	issue.Content = "content"
	issue.State = f3.IssueStateOpen
	issue.IsLocked = false
	issue.Created = created
	issue.Updated = updated
	issue.Closed = &closed

	return issue
}

func GeneratorModifyIssue(issue *f3.Issue, parent path.Path) []f3.Interface {
	assignees := issue.Assignees
	milestone := issue.Milestone
	labels := issue.Labels

	issue0 := issue.Clone().(*f3.Issue)
	issue0.Title = "modified " + issue.Title
	issue0.Content = "modified " + issue.Content

	issueClosed := issue0.Clone().(*f3.Issue)
	issueClosed.Assignees = []*f3.Reference{}
	issueClosed.Milestone = &f3.Reference{}
	issueClosed.Labels = []*f3.Reference{}
	issueClosed.State = f3.IssueStateClosed
	issueClosed.IsLocked = true

	issueOpen := issue0.Clone().(*f3.Issue)
	issueOpen.Assignees = assignees
	issueOpen.Milestone = milestone
	issueOpen.Labels = labels
	issueOpen.State = f3.IssueStateOpen
	issueClosed.IsLocked = false

	return []f3.Interface{
		issue0,
		issueClosed,
		issueOpen,
	}
}

func GeneratorSetRandomMilestone(milestone *f3.Milestone, parent path.Path) *f3.Milestone {
	now := now()
	created := tick(&now)
	updated := tick(&now)
	deadline := tick(&now)

	title := fmt.Sprintf("milestone%s", milestone.GetID())
	milestone.Title = title
	milestone.Description = title + " description"
	milestone.Deadline = &deadline
	milestone.Created = created
	milestone.Updated = &updated
	milestone.Closed = nil
	milestone.State = f3.MilestoneStateOpen

	return milestone
}

func GeneratorModifyMilestone(milestone *f3.Milestone, parent path.Path) []f3.Interface {
	milestone0 := milestone.Clone().(*f3.Milestone)
	milestone0.Title = "modified " + milestone.Title

	milestoneClosed := milestone0.Clone().(*f3.Milestone)
	milestoneClosed.State = f3.MilestoneStateClosed
	deadline := time.Now().Truncate(time.Second).Add(5 * time.Minute)
	milestoneClosed.Deadline = &deadline

	milestoneOpen := milestone0.Clone().(*f3.Milestone)
	milestoneOpen.State = f3.MilestoneStateOpen

	return []f3.Interface{
		milestone0,
		milestoneClosed,
		milestoneOpen,
	}
}

func GeneratorSetRandomTopic(topic *f3.Topic, parent path.Path) *f3.Topic {
	topic.Name = fmt.Sprintf("topic%s", topic.GetID())
	return topic
}

func GeneratorModifyTopic(topic *f3.Topic, parent path.Path) []f3.Interface {
	topic0 := topic.Clone().(*f3.Topic)
	return []f3.Interface{topic0}
}

func GeneratorSetRandomReaction(reaction *f3.Reaction, parent path.Path) *f3.Reaction {
	user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
	reaction.UserID = f3_tree.NewUserReference(user.GetID())
	reaction.Content = "laugh"
	return reaction
}

func GeneratorSetRandomLabel(label *f3.Label, parent path.Path) *f3.Label {
	name := fmt.Sprintf("label%s", label.GetID())
	label.Name = name
	label.Description = name + " description"
	label.Color = "ffffff"
	return label
}

func GeneratorModifyLabel(label *f3.Label, parent path.Path) []f3.Interface {
	label0 := label.Clone().(*f3.Label)
	label0.Name = "modified" + label.Name
	label0.Color = "f0f0f0"
	label0.Description = "modified " + label.Description
	return []f3.Interface{label0}
}

func GeneratorSetRandomComment(comment *f3.Comment, parent path.Path) *f3.Comment {
	user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))

	now := now()
	commentCreated := tick(&now)
	commentUpdated := tick(&now)

	comment.PosterID = f3_tree.NewUserReference(user.GetID())
	comment.Created = commentCreated
	comment.Updated = commentUpdated
	comment.Content = "comment content"
	return comment
}

func GeneratorModifyComment(comment *f3.Comment, parent path.Path) []f3.Interface {
	comment0 := comment.Clone().(*f3.Comment)
	comment0.Content = "modified" + comment.Content
	return []f3.Interface{comment0}
}

func GeneratorSetRandomRelease(t *testing.T, release *f3.Release, parent path.Path) *f3.Release {
	user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
	project := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
	repository := project.Find(generic.NewPathFromString("repositories/vcs"))
	repository.Get(context.Background())
	repositoryHelper := tests_repository.NewTestHelper(t, "", repository)

	now := now()
	releaseCreated := tick(&now)

	tag := fmt.Sprintf("release%s", release.GetID())
	repositoryHelper.CreateRepositoryTag(tag, "master")
	sha := repositoryHelper.GetRepositorySha("master")
	fmt.Printf("GeneratorSetRandomRelease %s %s\n", repository.GetCurrentPath(), repository.GetID())
	repositoryHelper.PushMirror()

	release.TagName = tag
	release.TargetCommitish = sha
	release.Name = tag + " name"
	release.Body = tag + " body"
	release.Draft = false
	release.Prerelease = false
	release.PublisherID = f3_tree.NewUserReference(user.GetID())
	release.Created = releaseCreated

	return release
}

func GeneratorModifyRelease(t *testing.T, release *f3.Release, parent path.Path) []f3.Interface {
	release0 := release.Clone().(*f3.Release)
	release0.Body = "modified " + release.Body
	return []f3.Interface{release0}
}

func GeneratorSetRandomAttachment(attachment *f3.Attachment, parent path.Path) *f3.Attachment {
	name := fmt.Sprintf("attachmentname%s", attachment.GetID())
	content := fmt.Sprintf("attachmentcontent%s", attachment.GetID())
	downloadURL := "downloadURL"
	now := now()
	attachmentCreated := tick(&now)

	size := len(content)
	downloadCount := int64(10)
	sha256 := fmt.Sprintf("%x", sha256.Sum256([]byte(content)))

	attachment.Name = name
	attachment.Size = int64(size)
	attachment.DownloadCount = downloadCount
	attachment.Created = attachmentCreated
	attachment.SHA256 = sha256
	attachment.DownloadURL = downloadURL
	attachment.DownloadFunc = func() io.ReadCloser {
		rc := io.NopCloser(strings.NewReader(content))
		return rc
	}

	return attachment
}

func GeneratorModifyAttachment(attachment *f3.Attachment, parent path.Path) []f3.Interface {
	attachment0 := attachment.Clone().(*f3.Attachment)
	attachment0.Name = "modified" + attachment.Name
	return []f3.Interface{attachment0}
}

func GeneratorSetRandomPullRequest(t *testing.T, pullRequest *f3.PullRequest, parent path.Path) *f3.PullRequest {
	user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
	projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
	repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
	repositoryNode.Get(context.Background())
	repositoryHelper := tests_repository.NewTestHelper(t, "", repositoryNode)

	mainRef := "master"
	mainSha := repositoryHelper.GetRepositorySha(mainRef)
	featureRef := "generatedfeature"
	repositoryHelper.InternalBranchRepositoryFeature(featureRef, featureRef+" content")
	featureSha := repositoryHelper.GetRepositorySha(featureRef)
	fmt.Printf("createPullRequest: master %s at main %s feature %s\n", repositoryHelper.GetBare(), mainSha, featureSha)
	repositoryHelper.PushMirror()

	now := now()
	prCreated := tick(&now)
	prUpdated := tick(&now)

	pullRequest.PosterID = f3_tree.NewUserReference(user.GetID())
	pullRequest.Title = featureRef + " pr title"
	pullRequest.Content = featureRef + " pr content"
	pullRequest.State = f3.PullRequestStateOpen
	pullRequest.IsLocked = false
	pullRequest.Created = prCreated
	pullRequest.Updated = prUpdated
	pullRequest.Closed = nil
	pullRequest.Merged = false
	pullRequest.MergedTime = nil
	pullRequest.MergeCommitSHA = ""
	pullRequest.Head = f3.PullRequestBranch{
		Ref:        featureRef,
		SHA:        featureSha,
		Repository: f3.NewReference("../../repository/vcs"),
	}
	pullRequest.Base = f3.PullRequestBranch{
		Ref:        mainRef,
		SHA:        mainSha,
		Repository: f3.NewReference("../../repository/vcs"),
	}
	return pullRequest
}

func GeneratorModifyPullRequest(t *testing.T, pullRequest *f3.PullRequest, parent path.Path) []f3.Interface {
	pullRequest0 := pullRequest.Clone().(*f3.PullRequest)
	pullRequest0.Title = "modified " + pullRequest.Title
	return []f3.Interface{pullRequest0}
}

func GeneratorSetReview(t *testing.T, review *f3.Review, parent path.Path) *f3.Review {
	user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))

	projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
	repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
	repositoryNode.Get(context.Background())
	repositoryHelper := tests_repository.NewTestHelper(t, "", repositoryNode)

	now := now()
	reviewCreated := tick(&now)

	featureSha := repositoryHelper.GetRepositorySha("feature")

	review.ReviewerID = f3_tree.NewUserReference(user.GetID())
	review.Official = true
	review.CommitID = featureSha
	review.Content = "the review content"
	review.CreatedAt = reviewCreated
	review.State = f3.ReviewStateCommented

	return review
}

func GeneratorSetReviewComment(t *testing.T, comment *f3.ReviewComment, parent path.Path) *f3.ReviewComment {
	user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))

	projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
	repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
	repositoryNode.Get(context.Background())
	repositoryHelper := tests_repository.NewTestHelper(t, "", repositoryNode)

	now := now()
	commentCreated := tick(&now)
	commentUpdated := tick(&now)

	featureSha := repositoryHelper.GetRepositorySha("feature")

	comment.Content = "comment content"
	comment.TreePath = "README.md"
	comment.DiffHunk = "@@ -108,7 +108,6 @@"
	comment.Line = 1
	comment.CommitID = featureSha
	comment.PosterID = f3_tree.NewUserReference(user.GetID())
	comment.CreatedAt = commentCreated
	comment.UpdatedAt = commentUpdated

	return comment
}

func GeneratorModifyReviewComment(t *testing.T, comment *f3.ReviewComment, parent path.Path) []f3.Interface {
	comment0 := comment.Clone().(*f3.ReviewComment)
	comment0.Content = "modified " + comment.Content
	return []f3.Interface{comment0}
}
