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

package forgejo

import (
	"context"
	"fmt"
	"strings"

	"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 project struct {
	common
	forgejoProject *forgejo_sdk.Repository
	forked         *f3.Reference
}

var _ f3_tree.ForgeDriverInterface = &project{}

func newProject() generic.NodeDriverInterface {
	return &project{}
}

func (o *project) SetNative(project any) {
	o.forgejoProject = project.(*forgejo_sdk.Repository)
}

func (o *project) GetNativeID() string {
	return fmt.Sprintf("%d", o.forgejoProject.ID)
}

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

func (o *project) getForkedReference() *f3.Reference {
	if !o.forgejoProject.Fork {
		return nil
	}

	if o.forked == nil {
		_, resp, err := o.getClient().GetOrg(o.forgejoProject.Parent.Owner.UserName)
		owners := "organizations"
		if resp.StatusCode == 404 {
			owners = "users"
		} else if err != nil {
			panic(fmt.Errorf("get org %v %w", o, err))
		}

		o.forked = f3_tree.NewProjectReference(owners, fmt.Sprintf("%d", o.forgejoProject.Parent.Owner.ID), fmt.Sprintf("%d", o.forgejoProject.Parent.ID))
	}

	return o.forked
}

func (o *project) ToFormat() f3.Interface {
	if o.forgejoProject == nil {
		return o.NewFormat()
	}
	return &f3.Project{
		Common:        f3.NewCommon(o.GetNativeID()),
		Name:          o.forgejoProject.Name,
		IsPrivate:     o.forgejoProject.Private,
		Description:   o.forgejoProject.Description,
		DefaultBranch: o.forgejoProject.DefaultBranch,
		Forked:        o.getForkedReference(),
	}
}

func (o *project) FromFormat(content f3.Interface) {
	project := content.(*f3.Project)

	o.forgejoProject = &forgejo_sdk.Repository{
		ID:            util.ParseInt(project.GetID()),
		Name:          project.Name,
		Private:       project.IsPrivate,
		Description:   project.Description,
		DefaultBranch: project.DefaultBranch,
	}
	o.forked = project.Forked
}

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

	var project *forgejo_sdk.Repository
	var err error
	var resp *forgejo_sdk.Response
	if node.GetID() != id.NilID {
		project, resp, err = o.getClient().GetRepoByID(node.GetID().Int64())
	} else {
		panic("GetID() == 0")
	}
	if resp.StatusCode == 404 {
		return false
	}
	if err != nil {
		if strings.Contains(err.Error(), "project not found") {
			return false
		}
		panic(fmt.Errorf("project %v %w", o, err))
	}
	o.forgejoProject = project
	return true
}

func (o *project) Put(ctx context.Context) id.NodeID {
	owner := f3_tree.GetOwner(o.GetNode()).ToFormat()
	var ownerName string
	var isOrganization bool
	switch v := owner.(type) {
	case *f3.User:
		ownerName = v.UserName
		o.maybeSudoName(ownerName)
	case *f3.Organization:
		ownerName = v.Name
		isOrganization = true
	default:
		panic(fmt.Errorf("Put unexpected type %T", owner))
	}
	defer o.notSudo()

	if o.forked == nil {
		var repo *forgejo_sdk.Repository
		var err error
		if isOrganization {
			repo, _, err = o.getClient().CreateOrgRepo(ownerName, forgejo_sdk.CreateRepoOption{
				Name:          o.forgejoProject.Name,
				Description:   o.forgejoProject.Description,
				Private:       o.forgejoProject.Private,
				DefaultBranch: o.forgejoProject.DefaultBranch,
			})
		} else {
			repo, _, err = o.getClient().CreateRepo(forgejo_sdk.CreateRepoOption{
				Name:          o.forgejoProject.Name,
				Description:   o.forgejoProject.Description,
				Private:       o.forgejoProject.Private,
				DefaultBranch: o.forgejoProject.DefaultBranch,
			})
		}
		if err != nil {
			panic(err)
		}
		o.forgejoProject = repo
		o.Trace("project created %d", o.forgejoProject.ID)
	} else {
		options := forgejo_sdk.CreateForkOption{
			Name: &o.forgejoProject.Name,
		}
		owner, project := f3_tree.ResolveProjectReference(ctx, o.getTree(), o.forked)
		repo, _, err := o.getClient().CreateFork(owner, project, options)
		if err != nil {
			panic(fmt.Errorf("CreateFork %s %s - %w", owner, project, err))
		}
		o.forgejoProject = repo
		o.Trace("forked project created %d", o.forgejoProject.ID)
	}
	return id.NewNodeID(o.GetNativeID())
}

func (o *project) Patch(context.Context) {
	owner := f3_tree.GetOwnerName(o.GetNode())
	repo, _, err := o.getClient().EditRepo(owner, o.forgejoProject.Name, forgejo_sdk.EditRepoOption{
		Description: &o.forgejoProject.Description,
	})
	if err != nil {
		panic(err)
	}
	o.forgejoProject = repo
	o.Trace("%d", o.forgejoProject.ID)
}

func (o *project) Delete(ctx context.Context) {
	_, err := o.getClient().DeleteRepo(o.forgejoProject.Owner.UserName, o.forgejoProject.Name)
	if err != nil {
		panic(err)
	}
}
