package scaffolder

import (
	"context"
	"fmt"

	"github.com/gobuffalo/genny/v2"

	"github.com/ignite/cli/v29/ignite/pkg/errors"
	"github.com/ignite/cli/v29/ignite/pkg/multiformatname"
	"github.com/ignite/cli/v29/ignite/templates/field"
	"github.com/ignite/cli/v29/ignite/templates/field/datatype"
	"github.com/ignite/cli/v29/ignite/templates/message"
)

// messageOptions represents configuration for the message scaffolding.
type messageOptions struct {
	description       string
	signer            string
	withoutSimulation bool
}

// newMessageOptions returns a messageOptions with default options.
func newMessageOptions(messageName string) messageOptions {
	return messageOptions{
		description: fmt.Sprintf("Broadcast message %s", messageName),
		signer:      "creator",
	}
}

// MessageOption configures the message scaffolding.
type MessageOption func(*messageOptions)

// WithDescription provides a custom description for the message CLI command.
func WithDescription(desc string) MessageOption {
	return func(m *messageOptions) {
		m.description = desc
	}
}

// WithSigner provides a custom signer name for the message.
func WithSigner(signer string) MessageOption {
	return func(m *messageOptions) {
		m.signer = signer
	}
}

// WithoutSimulation disables generating messages simulation.
func WithoutSimulation() MessageOption {
	return func(m *messageOptions) {
		m.withoutSimulation = true
	}
}

// AddMessage adds a new message to scaffolded app.
func (s Scaffolder) AddMessage(
	ctx context.Context,
	moduleName,
	msgName string,
	fields,
	resFields []string,
	options ...MessageOption,
) error {
	// Create the options
	scaffoldingOpts := newMessageOptions(msgName)
	for _, apply := range options {
		apply(&scaffoldingOpts)
	}

	// If no module is provided, we add the type to the app's module
	if moduleName == "" {
		moduleName = s.modpath.Package
	}
	mfName, err := multiformatname.NewName(moduleName, multiformatname.NoNumber)
	if err != nil {
		return err
	}
	moduleName = mfName.LowerCase

	name, err := multiformatname.NewName(msgName)
	if err != nil {
		return err
	}

	if err := checkComponentValidity(s.appPath, moduleName, name, false); err != nil {
		return err
	}

	// Check and parse provided fields
	if err := checkCustomTypes(
		ctx,
		s.appPath,
		s.modpath.Package,
		s.protoDir,
		moduleName,
		fields,
	); err != nil {
		return err
	}
	parsedMsgFields, err := field.ParseFields(fields, checkForbiddenMessageField, scaffoldingOpts.signer)
	if err != nil {
		return err
	}

	// Check and parse provided response fields
	if err := checkCustomTypes(
		ctx,
		s.appPath,
		s.modpath.Package,
		s.protoDir,
		moduleName,
		resFields,
	); err != nil {
		return err
	}
	parsedResFields, err := field.ParseFields(resFields, checkGoReservedWord, scaffoldingOpts.signer)
	if err != nil {
		return err
	}

	mfSigner, err := multiformatname.NewName(scaffoldingOpts.signer)
	if err != nil {
		return err
	}

	var (
		g    *genny.Generator
		opts = &message.Options{
			AppName:      s.modpath.Package,
			ProtoDir:     s.protoDir,
			ProtoVer:     "v1", // TODO(@julienrbrt): possibly in the future add flag to specify custom proto version.
			ModulePath:   s.modpath.RawPath,
			ModuleName:   moduleName,
			MsgName:      name,
			Fields:       parsedMsgFields,
			ResFields:    parsedResFields,
			MsgDesc:      scaffoldingOpts.description,
			MsgSigner:    mfSigner,
			NoSimulation: scaffoldingOpts.withoutSimulation,
		}
	)

	// Scaffold
	g, err = message.NewGenerator(opts)
	if err != nil {
		return err
	}

	return s.Run(g)
}

// checkForbiddenMessageField returns true if the name is forbidden as a message name.
func checkForbiddenMessageField(name string) error {
	mfName, err := multiformatname.NewName(name)
	if err != nil {
		return err
	}

	if mfName.LowerCase == datatype.TypeCustom {
		return errors.Errorf("%s is used by the message scaffolder", name)
	}

	return checkGoReservedWord(name)
}
