package uuid

import (
	"encoding/json"
	"fmt"
	"io"
	"strconv"
	"time"

	fuuid "github.com/gofrs/uuid/v5"
)

var Nil = UUID{}

type UUIDList = []UUID

type UUID struct {
	fuuid.UUID
}

func New() UUID {
	return UUID{fuuid.Must(fuuid.NewV7())}
}

func NewFromTime(t time.Time) UUID {
	gen := fuuid.NewGenWithOptions(
		fuuid.WithEpochFunc(func() time.Time { return t }),
	)
	return UUID{fuuid.Must(gen.NewV7())}
}

func NewP() *UUID {
	return &UUID{fuuid.Must(fuuid.NewV7())}
}

func FromString(text string) (UUID, error) {
	u, err := fuuid.FromString(text)
	if err != nil {
		return Nil, err
	}

	return UUID{u}, nil
}

func MustFromString(text string) UUID {
	u, err := fuuid.FromString(text)
	if err != nil {
		panic(err)
	}

	return UUID{u}
}

func FromBytes(input []byte) (UUID, error) {
	u, err := fuuid.FromBytes(input)
	if err != nil {
		return Nil, err
	}

	return UUID{u}, nil
}

func (a *UUID) UnmarshalJSON(b []byte) error {
	var s string
	if err := json.Unmarshal(b, &s); err != nil {
		return err
	}

	if s == "" {
		a.UUID = fuuid.Nil
		return nil
	}

	return a.UUID.Parse(s)
}

func (a UUID) MarshalJSON() ([]byte, error) {
	if a.IsNil() {
		return json.Marshal("")
	}

	return json.Marshal(a.UUID)
}

// UnmarshalGQL implements the graphql.Unmarshaler interface
func (u *UUID) UnmarshalGQL(v interface{}) error {
	id, ok := v.(string)
	if !ok {
		return fmt.Errorf("uuid must be a string")
	}

	return u.Parse(id)
}

// MarshalGQL implements the graphql.Marshaler interface
func (u UUID) MarshalGQL(w io.Writer) {
	b := []byte(strconv.Quote(u.String()))
	_, err := w.Write(b)
	if err != nil {
		panic(err)
	}
}