package ops2

import (
	"fmt"
	"io"

	"gitlink.org.cn/cloudream/common/pkgs/future"
	"gitlink.org.cn/cloudream/common/pkgs/ioswitch/dag"
	"gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec"
	"gitlink.org.cn/cloudream/common/pkgs/logger"
	"gitlink.org.cn/cloudream/common/utils/io2"
	clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/pool"
)

func init() {
	exec.UseOp[*PublicWrite]()
	exec.UseOp[*PublicRead]()
}

type PublicRead struct {
	Output     exec.VarID
	UserSpace  clitypes.UserSpaceDetail
	ObjectPath string
}

func (o *PublicRead) Execute(ctx *exec.ExecContext, e *exec.Executor) error {
	logger.
		WithField("Output", o.Output).
		WithField("UserSpace", o.UserSpace).
		WithField("ObjectPath", o.ObjectPath).
		Debug("public read")
	defer logger.Debug("public read end")

	stgPool, err := exec.GetValueByType[*pool.Pool](ctx)
	if err != nil {
		return fmt.Errorf("getting storage pool: %w", err)
	}

	store, err := stgPool.GetPublicStore(&o.UserSpace)
	if err != nil {
		return fmt.Errorf("getting public store of storage %v: %w", o.UserSpace, err)
	}

	stream, err := store.Read(o.ObjectPath)
	if err != nil {
		return fmt.Errorf("reading object %v: %w", o.ObjectPath, err)
	}

	fut := future.NewSetVoid()
	output := &exec.StreamValue{
		Stream: io2.AfterReadClosed(stream, func(closer io.ReadCloser) {
			fut.SetVoid()
		}),
	}

	e.PutVar(o.Output, output)
	return fut.Wait(ctx.Context)
}

func (o *PublicRead) String() string {
	return fmt.Sprintf("PublicRead %v:%v -> %v", o.UserSpace, o.ObjectPath, o.Output)
}

type PublicWrite struct {
	Input      exec.VarID
	UserSpace  clitypes.UserSpaceDetail
	ObjectPath string
}

func (o *PublicWrite) Execute(ctx *exec.ExecContext, e *exec.Executor) error {
	logger.
		WithField("Input", o.Input).
		Debugf("write file to public store")
	defer logger.Debugf("write file to public store finished")

	stgPool, err := exec.GetValueByType[*pool.Pool](ctx)
	if err != nil {
		return fmt.Errorf("getting storage pool: %w", err)
	}

	store, err := stgPool.GetPublicStore(&o.UserSpace)
	if err != nil {
		return fmt.Errorf("getting public store of storage %v: %w", o.UserSpace, err)
	}

	input, err := exec.BindVar[*exec.StreamValue](e, ctx.Context, o.Input)
	if err != nil {
		return err
	}
	defer input.Stream.Close()

	return store.Write(o.ObjectPath, input.Stream)
}

func (o *PublicWrite) String() string {
	return fmt.Sprintf("PublicWrite %v -> %v:%v", o.Input, o.UserSpace, o.ObjectPath)
}

type PublicReadNode struct {
	dag.NodeBase
	From       ioswitch2.From
	UserSpace  clitypes.UserSpaceDetail
	ObjectPath string
}

func (b *GraphNodeBuilder) NewPublicRead(from ioswitch2.From, userSpace clitypes.UserSpaceDetail, objPath string) *PublicReadNode {
	node := &PublicReadNode{
		From:       from,
		UserSpace:  userSpace,
		ObjectPath: objPath,
	}
	b.AddNode(node)

	node.OutputStreams().Init(node, 1)
	return node
}

func (t *PublicReadNode) GetFrom() ioswitch2.From {
	return t.From
}

func (t *PublicReadNode) Output() dag.StreamOutputSlot {
	return dag.StreamOutputSlot{
		Node:  t,
		Index: 0,
	}
}

func (t *PublicReadNode) GenerateOp() (exec.Op, error) {
	return &PublicRead{
		Output:     t.Output().Var().VarID,
		UserSpace:  t.UserSpace,
		ObjectPath: t.ObjectPath,
	}, nil
}

type PublicWriteNode struct {
	dag.NodeBase
	To         ioswitch2.To
	UserSpace  clitypes.UserSpaceDetail
	ObjectPath string
}

func (b *GraphNodeBuilder) NewPublicWrite(to ioswitch2.To, userSpace clitypes.UserSpaceDetail, objPath string) *PublicWriteNode {
	node := &PublicWriteNode{
		To:         to,
		UserSpace:  userSpace,
		ObjectPath: objPath,
	}
	b.AddNode(node)

	node.InputStreams().Init(1)
	return node
}

func (t *PublicWriteNode) GetTo() ioswitch2.To {
	return t.To
}

func (t *PublicWriteNode) SetInput(input *dag.StreamVar) {
	input.To(t, 0)
}

func (t *PublicWriteNode) Input() dag.StreamInputSlot {
	return dag.StreamInputSlot{
		Node:  t,
		Index: 0,
	}
}

func (t *PublicWriteNode) GenerateOp() (exec.Op, error) {
	return &PublicWrite{
		Input:      t.InputStreams().Get(0).VarID,
		UserSpace:  t.UserSpace,
		ObjectPath: t.ObjectPath,
	}, nil
}
