package codegen

var structTemplate = `type {{.StructName}} struct { {{range .Fields}}
{{.Description}}
	{{.Name}} {{.Type}} {{.Tag}}
{{end}}}`

var codeFileTemplate = `// This file is generated by tools, DO NOT EDIT.
package {{.PackageName}}

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/cloudcarver/anclax/pkg/zgen/apigen"
	"github.com/cloudcarver/anclax/pkg/taskcore"
	"github.com/cloudcarver/anclax/pkg/taskcore/worker"
	"github.com/cloudcarver/anclax/pkg/utils"
	"github.com/pkg/errors"
	"github.com/jackc/pgx/v5"
)

func init() {
	utils.Noop()
}

const ( {{range .Functions}}
	{{upperFirst .Name}} = "{{.Name}}" 
{{end}})



type TaskRunner interface { {{range .Functions}}
{{.Description}}
	Run{{upperFirst .Name}}(ctx context.Context, params *{{.ParameterType}}, overrides ...taskcore.TaskOverride) (int32, error)
{{.Description}}
	Run{{upperFirst .Name}}WithTx(ctx context.Context, tx pgx.Tx, params *{{.ParameterType}}, overrides ...taskcore.TaskOverride) (int32, error)
{{end}}}

type Client struct {
	taskStore taskcore.TaskStoreInterface
	now       func() time.Time
}

func NewTaskRunner(taskStore taskcore.TaskStoreInterface) TaskRunner {
	return &Client{
		taskStore: taskStore,
		now:       time.Now,
	}
}

{{range .Functions}}
func (c *Client) Run{{upperFirst .Name}}(ctx context.Context, params *{{.ParameterType}}, overrides ...taskcore.TaskOverride) (int32, error) {
	return c.run{{upperFirst .Name}}(ctx, c.taskStore, params, overrides...)
}

func (c *Client) Run{{upperFirst .Name}}WithTx(ctx context.Context, tx pgx.Tx, params *{{.ParameterType}}, overrides ...taskcore.TaskOverride) (int32, error) {
	return c.run{{upperFirst .Name}}(ctx, c.taskStore.WithTx(tx), params, overrides...)
}

func (c *Client) run{{upperFirst .Name}}(ctx context.Context, taskstore taskcore.TaskStoreInterface, params *{{.ParameterType}}, overrides ...taskcore.TaskOverride) (int32, error) {
	payload, err := params.Marshal()
	if err != nil {
		return 0, err
	}

	spec := apigen.TaskSpec{
		Type:    {{upperFirst .Name}},
		Payload: payload,
	}
	attributes := apigen.TaskAttributes{}
	{{if .Timeout }}attributes.Timeout = utils.Ptr("{{.Timeout}}"){{end}}
	{{if .RetryPolicy }}attributes.RetryPolicy = &apigen.TaskRetryPolicy{
		Interval:    "{{.RetryPolicy.Interval}}",
		MaxAttempts: {{.RetryPolicy.MaxAttempts}},
	}{{end}}
	{{if .Cronjob }}attributes.Cronjob = &apigen.TaskCronjob{
		CronExpression: "{{.Cronjob.CronExpression}}",
	}{{end}}
	task := &apigen.Task{
		Attributes: attributes,
		Spec:       spec,
		Status:     apigen.Pending,
	}
	{{if .Delay }}delay, err := time.ParseDuration("{{.Delay}}")
	if err != nil {
		return fmt.Errorf("failed to parse delay: %w", err)
	}
	task.StartedAt = utils.Ptr(c.now().Add(delay)){{end}}
	for _, override := range overrides {
		if err := override(task); err != nil {
			return 0, errors.Wrap(err, "failed to apply task override")
		}
	}
	taskID, err := taskstore.PushTask(ctx, task)
	if err != nil {
		return 0, err
	}
	return taskID, nil
}{{end}}

{{.StructDefs}}{{range .Functions}}
func (r *{{.ParameterType}}) Parse(spec json.RawMessage) error {
	return json.Unmarshal(spec, r)
}

func (r *{{.ParameterType}}) Marshal() (json.RawMessage, error) {
	return json.Marshal(r)
}{{end}}

type ExecutorInterface interface { {{range .Functions}}
{{.Description}}
	Execute{{upperFirst .Name}}(ctx context.Context, tx pgx.Tx, params *{{.ParameterType}}) error
{{if .Events}}{{if .Events.OnFailed}}
	// Hook called when {{.Name}} fails
	On{{upperFirst .Name}}Failed(ctx context.Context, taskID int32, params *{{.ParameterType}}, tx pgx.Tx) error{{end}}{{end}}
{{end}}}

type TaskHandler struct {
	executor ExecutorInterface

	externalTaskHandler []worker.TaskHandler
}

func NewTaskHandler(executor ExecutorInterface) worker.TaskHandler {
	return &TaskHandler{
		executor: executor,
	}
}

func (f *TaskHandler) RegisterTaskHandler(handler worker.TaskHandler) {
	f.externalTaskHandler = append(f.externalTaskHandler, handler)
}

func (f *TaskHandler) HandleTask(ctx context.Context, tx pgx.Tx, spec worker.TaskSpec) error {
	for _, handler := range f.externalTaskHandler {
		if err := handler.HandleTask(ctx, tx, spec); err != nil {
			if errors.Is(err, worker.ErrUnknownTaskType) {
				continue
			}
			return err
		}
		return nil
	}

	switch spec.GetType() { {{range .Functions}}
	case {{upperFirst .Name}}:
		var params {{.ParameterType}}
		if err := params.Parse(spec.GetPayload()); err != nil {
			return fmt.Errorf("failed to parse {{.Name}} parameters: %w", err)
		}
		return f.executor.Execute{{upperFirst .Name}}(ctx, tx, &params)
		{{end}}
	default:
		return errors.Wrapf(worker.ErrUnknownTaskType, "unknown task type: %s", spec.GetType())
	}
}

func (f *TaskHandler) OnTaskFailed(ctx context.Context, tx pgx.Tx, failedTaskSpec worker.TaskSpec, taskID int32) error {
	for _, handler := range f.externalTaskHandler {
		if err := handler.OnTaskFailed(ctx, tx, failedTaskSpec, taskID); err != nil {
			if errors.Is(err, worker.ErrUnknownTaskType) {
				continue
			}
			return err
		}
		return nil
	}

	// Call the appropriate OnXXXFailed hook method
	switch failedTaskSpec.GetType() { {{range .Functions}}{{if .Events}}{{if .Events.OnFailed}}
	case {{upperFirst .Name}}:
		var params {{.ParameterType}}
		if err := params.Parse(failedTaskSpec.GetPayload()); err != nil {
			return fmt.Errorf("failed to parse {{.Name}} parameters: %w", err)
		}
		return f.executor.On{{upperFirst .Name}}Failed(ctx, taskID, &params, tx){{end}}{{end}}{{end}}
	default:
		return nil // No hook configured for this task type
	}
}
`
