// This is not autogenerated.
package core

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"time"

	"gopkg.in/yaml.v3"
)

// NullTime replaces sql.NullTime with an implementation
// that supports proper JSON encoding/decoding.
type NullTime struct {
	Time  time.Time
	Valid bool // Valid is true if NullTime is not NULL
}

// NewTime returns a new, properly instantiated
// NullTime object.
func NewTime(t time.Time) NullTime {
	return NullTime{Time: t, Valid: true}
}

// Scan implements the Scanner interface.
//
// Besides the time.Time most drivers return, it also parses string/[]byte
// timestamps. Some SQL drivers surface timestamps as text rather than
// time.Time — notably SQLite, which returns a computed column such as
// DATETIME(MAX(...)) as a string. Without the string handling those values
// silently scanned as NULL (Valid=false), even though the predecessor type
// (meshery's sql.Time) parsed them.
//
// The conversion is intentionally additive: any value that previously scanned
// successfully behaves identically, and any unrecognized value (including an
// unparseable string) still falls back to Valid=false without returning an
// error — matching the prior lenient behavior, so no existing consumer can
// newly fail.
func (nt *NullTime) Scan(value any) error {
	switch v := value.(type) {
	case time.Time:
		nt.Time, nt.Valid = v, true
	case string:
		nt.scanString(v)
	case []byte:
		nt.scanString(string(v))
	default: // nil or any unexpected type
		nt.Time, nt.Valid = time.Time{}, false
	}
	return nil
}

// scanLayouts are the timestamp formats SQL drivers emit when handing back
// values as text, tried in order. Package-level to avoid re-allocating on
// every Scan (called per row).
var scanLayouts = []string{
	"2006-01-02 15:04:05.999999999-07:00", // SQLite, with offset
	"2006-01-02 15:04:05.999999999",       // SQLite, fractional seconds
	"2006-01-02 15:04:05",                  // SQLite DATETIME()
	time.RFC3339Nano,
	time.RFC3339,
}

// scanString parses a textual timestamp into the receiver. An empty or
// unparseable string yields Valid=false.
func (nt *NullTime) scanString(s string) {
	if s == "" {
		nt.Time, nt.Valid = time.Time{}, false
		return
	}
	for _, layout := range scanLayouts {
		if t, err := time.Parse(layout, s); err == nil {
			nt.Time, nt.Valid = t, true
			return
		}
	}
	nt.Time, nt.Valid = time.Time{}, false
}

// Value implements the driver Valuer interface.
func (nt NullTime) Value() (driver.Value, error) {
	if !nt.Valid {
		return nil, nil
	}
	return nt.Time, nil
}

// MarshalJSON marshals the underlying value to a
// proper JSON representation.
func (nt NullTime) MarshalJSON() ([]byte, error) {
	if nt.Valid {
		return json.Marshal(nt.Time)
	}
	return json.Marshal(nil)
}

// UnmarshalJSON will unmarshal a JSON value into
// the propert representation of that value.
// --
// This NullTime implementation supports unmarshaling from both:
// - A JSON time string (e.g., "2023-07-05T15:04:05Z")
// - A JSON object (e.g., {"Time":"0001-01-01T00:00:00Z","Valid":false})
// - A JSON null value
//
// we need support for JSON object as provider is using sql.NullTime now for DeletedAt field
// which is marshaled to json as such object
// and is returned from api
func (nt *NullTime) UnmarshalJSON(text []byte) error {
	nt.Valid = false

	if string(text) == "null" || len(text) == 0 {
		return nil
	}

	// we need this type to avoid stack overflow
	type TmpNullTime NullTime
	var obj TmpNullTime
	errObj := json.Unmarshal(text, &obj)
	if errObj == nil {
		nt.Time = obj.Time
		nt.Valid = obj.Valid
		return nil
	}

	var t time.Time
	errTime := t.UnmarshalJSON(text)
	if errTime == nil {
		nt.Time = t
		nt.Valid = true
		return nil
	}

	// Both attempts failed — combine errors
	return fmt.Errorf("failed to unmarshal NullTime: as object error: %v; as time string error: %v", errObj, errTime)
}

func (nt NullTime) MarshalYAML() (any, error) {
	if !nt.Valid {
		return nil, nil // YAML null
	}
	return nt.Time.Format(time.RFC3339), nil
}

func (nt *NullTime) UnmarshalYAML(value *yaml.Node) error {
	nt.Valid = false

	if value.Kind != yaml.ScalarNode {
		return fmt.Errorf("expected scalar node for time string, got: %v", value.Kind)
	}

	if value.Tag == "!!null" || value.Value == "" {
		// Optional: support YAML `null` or empty string as invalid time
		return nil
	}

	var t time.Time
	if err := value.Decode(&t); err != nil {
		return fmt.Errorf("failed to parse time: %w", err)
	}

	nt.Time = t
	nt.Valid = true
	return nil
}
