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

package forgejo

import (
	"context"
	"fmt"

	"code.forgejo.org/f3/gof3/v3/f3"
	"code.forgejo.org/f3/gof3/v3/id"
	f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
	"code.forgejo.org/f3/gof3/v3/util"

	forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)

type issue struct {
	common

	forgejoIssue *forgejo_sdk.Issue
}

var _ f3_tree.ForgeDriverInterface = &issue{}

func newIssue() generic.NodeDriverInterface {
	return &issue{}
}

func (o *issue) SetNative(issue any) {
	o.forgejoIssue = issue.(*forgejo_sdk.Issue)
}

func (o *issue) GetNativeID() string {
	return fmt.Sprintf("%d", o.forgejoIssue.Index)
}

func (o *issue) NewFormat() f3.Interface {
	node := o.GetNode()
	return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}

func (o *issue) ToFormat() f3.Interface {
	if o.forgejoIssue == nil {
		return o.NewFormat()
	}

	milestone := &f3.Reference{}
	if o.forgejoIssue.Milestone != nil {
		milestone = f3_tree.NewIssueMilestoneReference(o.forgejoIssue.Milestone.ID)
	}

	assignees := make([]*f3.Reference, 0, len(o.forgejoIssue.Assignees))
	for _, assignee := range o.forgejoIssue.Assignees {
		assignees = append(assignees, f3_tree.NewUserReference(assignee.ID))
	}

	labels := make([]*f3.Reference, 0, len(o.forgejoIssue.Labels))
	for _, label := range o.forgejoIssue.Labels {
		labels = append(labels, f3_tree.NewIssueLabelReference(label.ID))
	}

	return &f3.Issue{
		Title:     o.forgejoIssue.Title,
		Common:    f3.NewCommon(o.GetNativeID()),
		PosterID:  f3_tree.NewUserReference(o.forgejoIssue.Poster.ID),
		Assignees: assignees,
		Labels:    labels,
		Content:   o.forgejoIssue.Body,
		Milestone: milestone,
		State:     string(o.forgejoIssue.State),
		Created:   o.forgejoIssue.Created,
		Updated:   o.forgejoIssue.Updated,
		Closed:    o.forgejoIssue.Closed,
		IsLocked:  o.forgejoIssue.IsLocked,
	}
}

func (o *issue) FromFormat(content f3.Interface) {
	issue := content.(*f3.Issue)
	var milestone *forgejo_sdk.Milestone
	if issue.Milestone != nil {
		milestone = &forgejo_sdk.Milestone{
			ID: issue.Milestone.GetIDAsInt(),
		}
	}
	o.forgejoIssue = &forgejo_sdk.Issue{
		Title: issue.Title,
		Index: util.ParseInt(issue.GetID()),
		Poster: &forgejo_sdk.User{
			ID: issue.PosterID.GetIDAsInt(),
		},
		Body:      issue.Content,
		Milestone: milestone,
		State:     forgejo_sdk.StateType(issue.State),
		Created:   issue.Created,
		Updated:   issue.Updated,
		Closed:    issue.Closed,
		IsLocked:  issue.IsLocked,
	}

	assignees := make([]*forgejo_sdk.User, 0, 5)
	for _, assignee := range issue.Assignees {
		assignees = append(assignees, &forgejo_sdk.User{ID: assignee.GetIDAsInt()})
	}
	o.forgejoIssue.Assignees = assignees

	labels := make([]*forgejo_sdk.Label, 0, 5)
	for _, label := range issue.Labels {
		labels = append(labels, &forgejo_sdk.Label{ID: label.GetIDAsInt()})
	}
	o.forgejoIssue.Labels = labels
}

func (o *issue) Get(ctx context.Context) bool {
	node := o.GetNode()
	o.Trace("%s", node.GetID())

	owner := f3_tree.GetOwnerName(o.GetNode())
	project := f3_tree.GetProjectName(o.GetNode())

	issue, resp, err := o.getClient().GetIssue(owner, project, node.GetID().Int64())
	if resp.StatusCode == 404 {
		return false
	}
	if err != nil {
		panic(fmt.Errorf("issue %v %w", o, err))
	}
	o.forgejoIssue = issue
	return true
}

func usersToNames(ctx context.Context, tree f3_tree.TreeInterface, users []*forgejo_sdk.User) []string {
	names := make([]string, 0, len(users))
	for _, user := range users {
		names = append(names, f3_tree.GetUsernameFromID(ctx, tree, user.ID))
	}
	return names
}

func (o *issue) Put(ctx context.Context) id.NodeID {
	node := o.GetNode()
	o.Trace("%s", node.GetID())

	owner := f3_tree.GetOwnerName(o.GetNode())
	project := f3_tree.GetProjectName(o.GetNode())

	o.maybeSudoID(ctx, o.forgejoIssue.Poster.ID)
	defer o.notSudo()

	createIssueOption := forgejo_sdk.CreateIssueOption{
		Title:     o.forgejoIssue.Title,
		Body:      o.forgejoIssue.Body,
		Assignees: usersToNames(ctx, node.GetTree().(f3_tree.TreeInterface), o.forgejoIssue.Assignees),
		Labels:    labelListToIDs(o.forgejoIssue.Labels),
		Closed:    o.forgejoIssue.State == forgejo_sdk.StateClosed,
	}
	if milestone := maybeMilestoneID(o.forgejoIssue.Milestone); milestone != nil {
		createIssueOption.Milestone = *milestone
	}
	issue, _, err := o.getClient().CreateIssue(owner, project, createIssueOption)
	if err != nil {
		panic(err)
	}
	o.forgejoIssue = issue
	return id.NewNodeID(o.GetNativeID())
}

func (o *issue) Patch(ctx context.Context) {
	node := o.GetNode()
	o.Trace("%s", node.GetID())

	owner := f3_tree.GetOwnerName(o.GetNode())
	project := f3_tree.GetProjectName(o.GetNode())

	o.maybeSudoID(ctx, o.forgejoIssue.Poster.ID)
	defer o.notSudo()

	_, _, err := o.getClient().EditIssue(owner, project, node.GetID().Int64(), forgejo_sdk.EditIssueOption{
		Title:     o.forgejoIssue.Title,
		Body:      &o.forgejoIssue.Body,
		State:     &o.forgejoIssue.State,
		Milestone: maybeMilestoneID(o.forgejoIssue.Milestone),
		Assignees: usersToNames(ctx, node.GetTree().(f3_tree.TreeInterface), o.forgejoIssue.Assignees),
	})
	if err != nil {
		panic(fmt.Errorf("EditIssue %v %w", o, err))
	}

	_, _, err = o.getClient().ReplaceIssueLabels(owner, project, node.GetID().Int64(), forgejo_sdk.IssueLabelsOption{
		Labels: labelListToIDs(o.forgejoIssue.Labels),
	})
	if err != nil {
		panic(fmt.Errorf("ReplaceIssueLabels %v %w", o, err))
	}
}

func (o *issue) Delete(ctx context.Context) {
	node := o.GetNode()
	o.Trace("%s", node.GetID())

	owner := f3_tree.GetOwnerName(o.GetNode())
	project := f3_tree.GetProjectName(o.GetNode())

	_, err := o.getClient().DeleteIssue(owner, project, node.GetID().Int64())
	if err != nil {
		panic(err)
	}
}
