// 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"
	"time"

	"code.forgejo.org/f3/gof3/v3/f3"
	helpers_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/repository"
	tests_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository"
	"code.forgejo.org/f3/gof3/v3/kind"
	"code.forgejo.org/f3/gof3/v3/logger"
	"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"

	"github.com/stretchr/testify/require"
)

var RootUser *f3.User = &f3.User{
	UserName: "root",
}

type Creator struct {
	logger logger.Interface
	t      TestingT
	d      string
	name   string
	serial int
}

func now() time.Time {
	return time.Now().Truncate(time.Second)
}

func tick(now *time.Time) time.Time {
	*now = now.Add(1 * time.Minute)
	return *now
}

func NewCreator(t TestingT, name string, logger logger.Interface) *Creator {
	return &Creator{
		t:      t,
		d:      t.TempDir(),
		logger: logger,
		name:   name,
	}
}

func (f *Creator) randomString(prefix string) string {
	f.serial++
	return fmt.Sprintf("%s%s%015d", prefix, f.name, f.serial)
}

func (f *Creator) GetDirectory() string {
	return f.d
}

func (f *Creator) Generate(k kind.Kind, parent path.Path) f3.Interface {
	switch k {
	case f3_tree.KindForge:
		return f.GenerateForge()
	case f3_tree.KindUser:
		return f.GenerateUser()
	case f3_tree.KindOrganization:
		return f.GenerateOrganization()
	case f3_tree.KindProject:
		return f.GenerateProject()
	case f3_tree.KindIssue:
		return f.GenerateIssue(parent)
	case f3_tree.KindMilestone:
		return f.GenerateMilestone()
	case f3_tree.KindTopic:
		return f.GenerateTopic()
	case f3_tree.KindReaction:
		return f.GenerateReaction(parent)
	case f3_tree.KindLabel:
		return f.GenerateLabel()
	case f3_tree.KindComment:
		return f.GenerateComment(parent)
	case f3_tree.KindRepository:
		return f.GenerateRepository(f3.RepositoryNameDefault)
	case f3_tree.KindRelease:
		return f.GenerateRelease(parent)
	case f3_tree.KindAttachment:
		return f.GenerateAttachment(parent)
	case f3_tree.KindPullRequest:
		return f.GeneratePullRequest(parent)
	case f3_tree.KindReview:
		return f.GenerateReview(parent)
	case f3_tree.KindReviewComment:
		return f.GenerateReviewComment(parent)
	default:
		panic(fmt.Errorf("not implemented %s", k))
	}
}

func (f *Creator) GenerateForge() *f3.Forge {
	return &f3.Forge{
		Common: f3.NewCommon("forge"),
	}
}

func (f *Creator) GenerateUser() *f3.User {
	username := f.randomString("user")
	return &f3.User{
		Name:     username + " Doe",
		UserName: username,
		Email:    username + "@example.com",
		Password: "Wrobyak4",
	}
}

func (f *Creator) GenerateOrganization() *f3.Organization {
	orgname := f.randomString("org")
	return &f3.Organization{
		FullName: orgname + " Lambda",
		Name:     orgname,
	}
}

func (f *Creator) GenerateProject() *f3.Project {
	projectname := f.randomString("project")

	return &f3.Project{
		Name:          projectname,
		IsPrivate:     false,
		IsMirror:      false,
		Description:   "project description",
		DefaultBranch: "master",
	}
}

func (f *Creator) GenerateForkedProject(parent path.Path, forked string) *f3.Project {
	project := f.GenerateProject()
	project.Forked = f3.NewReference(forked)
	return project
}

func (f *Creator) GenerateRepository(name string) *f3.Repository {
	repository := &f3.Repository{
		Name: name,
	}
	p := f.t.TempDir()
	helper := tests_repository.NewTestHelper(f.t, p, nil)
	helper.CreateRepositoryContent("").PushMirror()
	repository.FetchFunc = func(ctx context.Context, destination, internalRef string) {
		f.logger.Debug("%s %s", p, destination)
		helpers_repository.GitMirror(context.Background(), nil, p, destination, internalRef)
	}
	return repository
}

func (f *Creator) GenerateReaction(parent path.Path) *f3.Reaction {
	user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
	return &f3.Reaction{
		UserID:  f3_tree.NewUserReference(user.GetID()),
		Content: "heart",
	}
}

func (f *Creator) GenerateMilestone() *f3.Milestone {
	now := now()
	created := tick(&now)
	updated := tick(&now)
	deadline := tick(&now)

	return &f3.Milestone{
		Title:       "milestone1",
		Description: "milestone1 description",
		Deadline:    &deadline,
		Created:     created,
		Updated:     &updated,
		Closed:      nil,
		State:       f3.MilestoneStateOpen,
	}
}

func (f *Creator) GeneratePullRequest(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(f.t, "", repositoryNode)

	mainRef := "master"
	mainSha := repositoryHelper.GetRepositorySha(mainRef)
	featureRef := "feature"
	repositoryHelper.InternalBranchRepositoryFeature(featureRef, "feature content")
	featureSha := repositoryHelper.GetRepositorySha(featureRef)
	f.logger.Debug("master %s at main %s feature %s", repositoryHelper.GetBare(), mainSha, featureSha)
	repositoryHelper.PushMirror()

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

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

func (f *Creator) GenerateLabel() *f3.Label {
	name := f.randomString("label")
	return &f3.Label{
		Name:        name,
		Color:       "ffffff",
		Description: name + " description",
	}
}

func (f *Creator) GenerateTopic() *f3.Topic {
	return &f3.Topic{
		Name: "topic1",
	}
}

func (f *Creator) GenerateIssue(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"))
	require.NotEqualValues(f.t, generic.NilNode, labelsNode)
	labels := labelsNode.GetChildren()
	require.NotEmpty(f.t, labels)
	firstLabel := labels[0]

	milestonesNode := projectNode.Find(generic.NewPathFromString("milestones"))
	require.NotEqualValues(f.t, generic.NilNode, milestonesNode)
	milestones := milestonesNode.GetChildren()
	require.NotEmpty(f.t, milestones)
	firstMilestone := milestones[0]

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

	return &f3.Issue{
		PosterID:  f3_tree.NewUserReference(user.GetID()),
		Assignees: []*f3.Reference{f3_tree.NewUserReference(user.GetID())},
		Labels:    []*f3.Reference{f3_tree.NewIssueLabelReference(firstLabel.GetID())},
		Milestone: f3_tree.NewIssueMilestoneReference(firstMilestone.GetID()),
		Title:     "title",
		Content:   "content",
		State:     f3.IssueStateOpen,
		IsLocked:  false,
		Created:   created,
		Updated:   updated,
		Closed:    &closed,
	}
}

func (f *Creator) GenerateComment(parent path.Path) *f3.Comment {
	user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))

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

	posterID := f3_tree.NewUserReference(user.GetID())

	return &f3.Comment{
		PosterID: posterID,
		Created:  commentCreated,
		Updated:  commentUpdated,
		Content:  "comment content",
	}
}

func (f *Creator) GenerateRelease(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(f.t, "", repository)

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

	tag := "releasetagv12"
	repositoryHelper.CreateRepositoryTag(tag, "master")
	sha := repositoryHelper.GetRepositorySha("master")
	repositoryHelper.PushMirror()

	return &f3.Release{
		TagName:         tag,
		TargetCommitish: sha,
		Name:            "v12 name",
		Body:            "v12 body",
		Draft:           false,
		Prerelease:      false,
		PublisherID:     f3_tree.NewUserReference(user.GetID()),
		Created:         releaseCreated,
	}
}

func (f *Creator) GenerateAttachment(parent path.Path) *f3.Attachment {
	name := "attachmentname"
	content := "attachmentcontent"
	downloadURL := "downloadURL"
	now := now()
	attachmentCreated := tick(&now)

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

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

func (f *Creator) GenerateReview(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(f.t, "", repositoryNode)

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

	featureSha := repositoryHelper.GetRepositorySha("feature")

	return &f3.Review{
		ReviewerID: f3_tree.NewUserReference(user.GetID()),
		Official:   true,
		CommitID:   featureSha,
		Content:    "the review content",
		CreatedAt:  reviewCreated,
		State:      f3.ReviewStateCommented,
	}
}

func (f *Creator) GenerateReviewComment(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(f.t, "", repositoryNode)

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

	featureSha := repositoryHelper.GetRepositorySha("feature")

	return &f3.ReviewComment{
		Content:   "comment content",
		TreePath:  "README.md",
		DiffHunk:  "@@ -108,7 +108,6 @@",
		Line:      1,
		CommitID:  featureSha,
		PosterID:  f3_tree.NewUserReference(user.GetID()),
		CreatedAt: commentCreated,
		UpdatedAt: commentUpdated,
	}
}
