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

package forgejo

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"

	"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 attachment struct {
	common

	forgejoAttachment *forgejo_sdk.Attachment
	sha               string
	contentType       string
	downloadFunc      f3.DownloadFuncType
}

var _ f3_tree.ForgeDriverInterface = &attachment{}

func newAttachment() generic.NodeDriverInterface {
	return &attachment{}
}

func (o *attachment) SetNative(attachment any) {
	o.forgejoAttachment = attachment.(*forgejo_sdk.Attachment)
}

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

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

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

	if o.sha == "" {
		// the API does not provide the SHA256, save the attachment in a temporary file
		// to get it
		objectsHelper := o.getF3Tree().GetObjectsHelper()

		o.Trace("download from %s", o.forgejoAttachment.DownloadURL)
		req, err := http.NewRequest("GET", o.forgejoAttachment.DownloadURL, nil)
		if err != nil {
			panic(err)
		}
		httpClient := o.getNewMigrationHTTPClient()()
		resp, err := httpClient.Do(req)
		if err != nil {
			panic(fmt.Errorf("while downloading %s %w", o.forgejoAttachment.DownloadURL, err))
		}

		sha, path := objectsHelper.Save(resp.Body)
		o.sha = sha

		o.downloadFunc = func() io.ReadCloser {
			o.Trace("download %s from copy stored in temporary file %s", o.forgejoAttachment.DownloadURL, path)
			f, err := os.Open(path)
			if err != nil {
				panic(err)
			}
			return f
		}
	}

	return &f3.Attachment{
		Common:        f3.NewCommon(o.GetNativeID()),
		Name:          o.forgejoAttachment.Name,
		Size:          o.forgejoAttachment.Size,
		ContentType:   o.contentType,
		DownloadCount: o.forgejoAttachment.DownloadCount,
		SHA256:        o.sha,
		DownloadURL:   o.forgejoAttachment.DownloadURL,
		Created:       o.forgejoAttachment.Created,
		DownloadFunc:  o.downloadFunc,
	}
}

func (o *attachment) FromFormat(content f3.Interface) {
	attachment := content.(*f3.Attachment)
	o.forgejoAttachment = &forgejo_sdk.Attachment{
		ID:            util.ParseInt(attachment.GetID()),
		Name:          attachment.Name,
		Size:          attachment.Size,
		DownloadCount: attachment.DownloadCount,
		Created:       attachment.Created,
		DownloadURL:   attachment.DownloadURL,
	}

	o.sha = attachment.SHA256
	o.contentType = attachment.ContentType
	o.downloadFunc = attachment.DownloadFunc
}

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

	var err error
	var attachment *forgejo_sdk.Attachment
	var resp *forgejo_sdk.Response

	owner := f3_tree.GetOwnerName(o.GetNode())
	project := f3_tree.GetProjectName(o.GetNode())
	attachable := f3_tree.GetAttachable(o.GetNode())
	attachableID := f3_tree.GetAttachableID(o.GetNode())

	switch attachable.GetKind() {
	case f3_tree.KindRelease:
		attachment, resp, err = o.getClient().GetReleaseAttachment(owner, project, attachableID, node.GetID().Int64())
	case f3_tree.KindComment:
		attachment, resp, err = o.getClient().GetCommentAttachment(owner, project, attachableID, node.GetID().Int64())
	case f3_tree.KindIssue, f3_tree.KindPullRequest:
		attachment, resp, err = o.getClient().GetIssueAttachment(owner, project, attachableID, node.GetID().Int64())
	default:
		panic(fmt.Errorf("unexpected type %s", attachable.GetKind()))
	}
	if resp.StatusCode == 404 {
		return false
	}
	if err != nil {
		panic(fmt.Errorf("attachment %v %w", o, err))
	}
	o.forgejoAttachment = attachment
	return true
}

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

	var err error
	var attachment *forgejo_sdk.Attachment

	owner := f3_tree.GetOwnerName(o.GetNode())
	project := f3_tree.GetProjectName(o.GetNode())
	attachable := f3_tree.GetAttachable(o.GetNode())
	attachableID := f3_tree.GetAttachableID(o.GetNode())

	switch attachable.GetKind() {
	case f3_tree.KindRelease:
		attachment, _, err = o.getClient().CreateReleaseAttachment(owner, project, attachableID, o.downloadFunc(), o.forgejoAttachment.Name)
	case f3_tree.KindComment:
		attachment, _, err = o.getClient().CreateCommentAttachment(owner, project, attachableID, o.downloadFunc(), o.forgejoAttachment.Name)
	case f3_tree.KindIssue, f3_tree.KindPullRequest:
		attachment, _, err = o.getClient().CreateIssueAttachment(owner, project, attachableID, o.downloadFunc(), o.forgejoAttachment.Name)
	default:
		panic(fmt.Errorf("unexpected type %s", attachable.GetKind()))
	}
	if err != nil {
		panic(err)
	}
	o.forgejoAttachment = attachment
	o.sha = ""
	return id.NewNodeID(o.GetNativeID())
}

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

	var err error

	owner := f3_tree.GetOwnerName(o.GetNode())
	project := f3_tree.GetProjectName(o.GetNode())
	attachable := f3_tree.GetAttachable(o.GetNode())
	attachableID := f3_tree.GetAttachableID(o.GetNode())

	opt := forgejo_sdk.EditAttachmentOptions{
		Name: o.forgejoAttachment.Name,
	}

	switch attachable.GetKind() {
	case f3_tree.KindRelease:
		_, _, err = o.getClient().EditReleaseAttachment(owner, project, attachableID, node.GetID().Int64(), opt)
	case f3_tree.KindComment:
		_, _, err = o.getClient().EditCommentAttachment(owner, project, attachableID, node.GetID().Int64(), opt)
	case f3_tree.KindIssue, f3_tree.KindPullRequest:
		_, _, err = o.getClient().EditIssueAttachment(owner, project, attachableID, node.GetID().Int64(), opt)
	default:
		panic(fmt.Errorf("unexpected type %s", attachable.GetKind()))
	}
	if err != nil {
		panic(err)
	}
}

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

	var err error

	owner := f3_tree.GetOwnerName(o.GetNode())
	project := f3_tree.GetProjectName(o.GetNode())
	attachable := f3_tree.GetAttachable(o.GetNode())
	attachableID := f3_tree.GetAttachableID(o.GetNode())

	switch attachable.GetKind() {
	case f3_tree.KindRelease:
		_, err = o.getClient().DeleteReleaseAttachment(owner, project, attachableID, node.GetID().Int64())
	case f3_tree.KindComment:
		_, err = o.getClient().DeleteCommentAttachment(owner, project, attachableID, node.GetID().Int64())
	case f3_tree.KindIssue, f3_tree.KindPullRequest:
		_, err = o.getClient().DeleteIssueAttachment(owner, project, attachableID, node.GetID().Int64())
	default:
		panic(fmt.Errorf("unexpected type %s", attachable.GetKind()))
	}
	if err != nil {
		panic(err)
	}
}
