package smartcontract

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"

	"github.com/nspcc-dev/neo-go/cli/cmdargs"
	"github.com/nspcc-dev/neo-go/cli/flags"
	"github.com/nspcc-dev/neo-go/pkg/core/state"
	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
	"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/urfave/cli"
)

func manifestAddGroup(ctx *cli.Context) error {
	if err := cmdargs.EnsureNone(ctx); err != nil {
		return err
	}
	sender, err := flags.ParseAddress(ctx.String("sender"))
	if err != nil {
		return cli.NewExitError(fmt.Errorf("invalid sender: %w", err), 1)
	}

	nf, _, err := readNEFFile(ctx.String("nef"))
	if err != nil {
		return cli.NewExitError(fmt.Errorf("can't read NEF file: %w", err), 1)
	}

	mPath := ctx.String("manifest")
	m, _, err := readManifest(mPath, util.Uint160{})
	if err != nil {
		return cli.NewExitError(fmt.Errorf("can't read contract manifest: %w", err), 1)
	}

	h := state.CreateContractHash(sender, nf.Checksum, m.Name)

	gAcc, w, err := getAccFromContext(ctx)
	if err != nil {
		return cli.NewExitError(fmt.Errorf("can't get account to sign group with: %w", err), 1)
	}
	defer w.Close()

	var found bool

	sig := gAcc.PrivateKey().Sign(h.BytesBE())
	pub := gAcc.PublicKey()
	for i := range m.Groups {
		if m.Groups[i].PublicKey.Equal(pub) {
			m.Groups[i].Signature = sig
			found = true
			break
		}
	}
	if !found {
		m.Groups = append(m.Groups, manifest.Group{
			PublicKey: pub,
			Signature: sig,
		})
	}

	rawM, err := json.Marshal(m)
	if err != nil {
		return cli.NewExitError(fmt.Errorf("can't marshal manifest: %w", err), 1)
	}

	err = os.WriteFile(mPath, rawM, os.ModePerm)
	if err != nil {
		return cli.NewExitError(fmt.Errorf("can't write manifest file: %w", err), 1)
	}
	return nil
}

func readNEFFile(filename string) (*nef.File, []byte, error) {
	if len(filename) == 0 {
		return nil, nil, errors.New("no nef file was provided")
	}

	f, err := os.ReadFile(filename)
	if err != nil {
		return nil, nil, err
	}

	nefFile, err := nef.FileFromBytes(f)
	if err != nil {
		return nil, nil, fmt.Errorf("can't parse NEF file: %w", err)
	}

	return &nefFile, f, nil
}

// readManifest unmarshalls manifest got from the provided filename and checks
// it for validness against the provided contract hash. If empty hash is specified
// then no hash-related manifest groups check is performed.
func readManifest(filename string, hash util.Uint160) (*manifest.Manifest, []byte, error) {
	if len(filename) == 0 {
		return nil, nil, errNoManifestFile
	}

	manifestBytes, err := os.ReadFile(filename)
	if err != nil {
		return nil, nil, err
	}

	m := new(manifest.Manifest)
	err = json.Unmarshal(manifestBytes, m)
	if err != nil {
		return nil, nil, err
	}
	if err := m.IsValid(hash); err != nil {
		return nil, nil, fmt.Errorf("manifest is invalid: %w", err)
	}
	return m, manifestBytes, nil
}