package circuits

import (
	"encoding/json"
	"fmt"
	"os"
	"path"
	"time"

	"github.com/consensys/gnark-crypto/ecc"
)

// SetupManifest is the human-readable manifest of the assets generated by the prover setup command
type SetupManifest struct {
	CircuitName string    `json:"circuitName"`
	Timestamp   time.Time `json:"timestamp"`

	// Sha256 of the associated assets
	Checksums struct {
		VerifyingKey     string `json:"verifyingKey"`
		VerifierContract string `json:"verifierContract"`
		Circuit          string `json:"circuit"`
	} `json:"checksums"`

	NbConstraints int            `json:"nbConstraints"`
	CurveID       string         `json:"curveID"`
	ExtraFlags    map[string]any `json:"extraFlags"`
}

// NewSetupManifest creates a new manifest.
func NewSetupManifest(
	circuitName string,
	nbConstraints int,
	curveID ecc.ID,
	extraFlags map[string]any,
) SetupManifest {

	return SetupManifest{
		CircuitName:   circuitName,
		Timestamp:     time.Now(),
		NbConstraints: nbConstraints,
		CurveID:       curveID.String(),
		ExtraFlags:    extraFlags,
	}
}

// GetInt returns the value of an int flag set in the ExtraFlags map
func (m *SetupManifest) GetInt(key string) (int, error) {
	v, err := m.get(key)
	if err != nil {
		return 0, err
	}

	switch x := v.(type) {
	case int:
		return x, nil
	case float64:
		if i := int(x); float64(i) == x {
			return i, nil
		}
	}

	return 0, fmt.Errorf("flag `%s` is not an int", key)
}

func (m *SetupManifest) GetString(key string) (string, error) {
	v, err := m.get(key)
	if err != nil {
		return "", err
	}

	s, ok := v.(string)
	if !ok {
		return "", fmt.Errorf("flag `%s` is not a string", key)
	}

	return s, nil
}

func (m *SetupManifest) GetStringArray(key string) ([]string, error) {
	v, err := m.get(key)
	if err != nil {
		return nil, err
	}

	s, ok := v.([]any)
	if !ok {
		return nil, fmt.Errorf("flag `%s` is not a any array", key)
	}

	r := make([]string, len(s))
	for i, v := range s {
		r[i], ok = v.(string)
		if !ok {
			return nil, fmt.Errorf("flag `%s` is not a string array", key)
		}
	}

	return r, nil
}

func (m *SetupManifest) get(key string) (any, error) {
	if m.ExtraFlags == nil {
		return nil, fmt.Errorf("extra flags not set")
	}

	v, ok := m.ExtraFlags[key]
	if !ok {
		return nil, fmt.Errorf("flag `%s` not set", key)
	}

	return v, nil
}

// ReadSetupManifestFromFile reads a manifest from a json file
func ReadSetupManifest(filePath string) (*SetupManifest, error) {
	f, err := os.Open(filePath)
	if err != nil {
		return nil, fmt.Errorf("opening manifest file: %w", err)
	}
	defer f.Close()

	var m SetupManifest
	if err := json.NewDecoder(f).Decode(&m); err != nil {
		return nil, fmt.Errorf("decoding manifest file: %w", err)
	}

	return &m, nil
}

// writeToJSONFile writes a manifest to a json file
func (m *SetupManifest) WriteTo(filePath string) error {

	if err := os.MkdirAll(path.Dir(filePath), 0755); err != nil {
		return fmt.Errorf("could not create dir `%v`: %w", path.Dir(filePath), err)
	}

	b, err := json.MarshalIndent(m, "", "  ")
	if err != nil {
		return fmt.Errorf("encoding manifest to JSON: %s", err)
	}

	f, err := os.Create(filePath)
	if err != nil {
		return fmt.Errorf("creating output JSON file: %s", err)
	}
	defer f.Close()

	if _, err = f.Write(b); err != nil {
		return fmt.Errorf("writing manifest to file: %s", err)
	}

	return nil
}
