package pgtype

import (
	
	
	
	
	
	

	
)

const (
	pgTimestampFormat = "2006-01-02 15:04:05.999999999"
	jsonISO8601       = "2006-01-02T15:04:05.999999999"
)

type TimestampScanner interface {
	ScanTimestamp(v Timestamp) error
}

type TimestampValuer interface {
	TimestampValue() (Timestamp, error)
}

// Timestamp represents the PostgreSQL timestamp type.
type Timestamp struct {
	Time             time.Time // Time zone will be ignored when encoding to PostgreSQL.
	InfinityModifier InfinityModifier
	Valid            bool
}

// ScanTimestamp implements the [TimestampScanner] interface.
func ( *Timestamp) ( Timestamp) error {
	* = 
	return nil
}

// TimestampValue implements the [TimestampValuer] interface.
func ( Timestamp) () (Timestamp, error) {
	return , nil
}

// Scan implements the [database/sql.Scanner] interface.
func ( *Timestamp) ( any) error {
	if  == nil {
		* = Timestamp{}
		return nil
	}

	switch src := .(type) {
	case string:
		return (&scanPlanTextTimestampToTimestampScanner{}).Scan([]byte(), )
	case time.Time:
		* = Timestamp{Time: , Valid: true}
		return nil
	}

	return fmt.Errorf("cannot scan %T", )
}

// Value implements the [database/sql/driver.Valuer] interface.
func ( Timestamp) () (driver.Value, error) {
	if !.Valid {
		return nil, nil
	}

	if .InfinityModifier != Finite {
		return .InfinityModifier.String(), nil
	}
	return .Time, nil
}

// MarshalJSON implements the [encoding/json.Marshaler] interface.
func ( Timestamp) () ([]byte, error) {
	if !.Valid {
		return []byte("null"), nil
	}

	var  string

	switch .InfinityModifier {
	case Finite:
		 = .Time.Format(jsonISO8601)
	case Infinity:
		 = "infinity"
	case NegativeInfinity:
		 = "-infinity"
	}

	return json.Marshal()
}

// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface.
func ( *Timestamp) ( []byte) error {
	var  *string
	 := json.Unmarshal(, &)
	if  != nil {
		return 
	}

	if  == nil {
		* = Timestamp{}
		return nil
	}

	switch * {
	case "infinity":
		* = Timestamp{Valid: true, InfinityModifier: Infinity}
	case "-infinity":
		* = Timestamp{Valid: true, InfinityModifier: -Infinity}
	default:
		// Parse time with or without timezone
		 := *
		// PostgreSQL uses ISO 8601 without timezone for to_json function and casting from a string to timestamp
		,  := time.Parse(time.RFC3339Nano, )
		if  == nil {
			* = Timestamp{Time: , Valid: true}
			return nil
		}
		,  = time.ParseInLocation(jsonISO8601, , time.UTC)
		if  == nil {
			* = Timestamp{Time: , Valid: true}
			return nil
		}
		.Valid = false
		return fmt.Errorf("cannot unmarshal %s to timestamp with layout %s or %s (%w)",
			*, time.RFC3339Nano, jsonISO8601, )
	}
	return nil
}

type TimestampCodec struct {
	// ScanLocation is the location that the time is assumed to be in for scanning. This is different from
	// TimestamptzCodec.ScanLocation in that this setting does change the instant in time that the timestamp represents.
	ScanLocation *time.Location
}

func (*TimestampCodec) ( int16) bool {
	return  == TextFormatCode ||  == BinaryFormatCode
}

func (*TimestampCodec) () int16 {
	return BinaryFormatCode
}

func (*TimestampCodec) ( *Map,  uint32,  int16,  any) EncodePlan {
	if ,  := .(TimestampValuer); ! {
		return nil
	}

	switch  {
	case BinaryFormatCode:
		return encodePlanTimestampCodecBinary{}
	case TextFormatCode:
		return encodePlanTimestampCodecText{}
	}

	return nil
}

type encodePlanTimestampCodecBinary struct{}

func (encodePlanTimestampCodecBinary) ( any,  []byte) ( []byte,  error) {
	,  := .(TimestampValuer).TimestampValue()
	if  != nil {
		return nil, 
	}

	if !.Valid {
		return nil, nil
	}

	var  int64
	switch .InfinityModifier {
	case Finite:
		 := discardTimeZone(.Time)
		 := .Unix()*1_000_000 + int64(.Nanosecond())/1000
		 =  - microsecFromUnixEpochToY2K
	case Infinity:
		 = infinityMicrosecondOffset
	case NegativeInfinity:
		 = negativeInfinityMicrosecondOffset
	}

	 = pgio.AppendInt64(, )

	return , nil
}

type encodePlanTimestampCodecText struct{}

func (encodePlanTimestampCodecText) ( any,  []byte) ( []byte,  error) {
	,  := .(TimestampValuer).TimestampValue()
	if  != nil {
		return nil, 
	}

	if !.Valid {
		return nil, nil
	}

	var  string

	switch .InfinityModifier {
	case Finite:
		 := discardTimeZone(.Time)

		// Year 0000 is 1 BC
		 := false
		if  := .Year();  <= 0 {
			 = - + 1
			 = time.Date(, .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), time.UTC)
			 = true
		}

		 = .Truncate(time.Microsecond).Format(pgTimestampFormat)

		if  {
			 =  + " BC"
		}
	case Infinity:
		 = "infinity"
	case NegativeInfinity:
		 = "-infinity"
	}

	 = append(, ...)

	return , nil
}

func ( time.Time) time.Time {
	if .Location() != time.UTC {
		return time.Date(.Year(), .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), time.UTC)
	}

	return 
}

func ( *TimestampCodec) ( *Map,  uint32,  int16,  any) ScanPlan {
	switch  {
	case BinaryFormatCode:
		switch .(type) {
		case TimestampScanner:
			return &scanPlanBinaryTimestampToTimestampScanner{location: .ScanLocation}
		}
	case TextFormatCode:
		switch .(type) {
		case TimestampScanner:
			return &scanPlanTextTimestampToTimestampScanner{location: .ScanLocation}
		}
	}

	return nil
}

type scanPlanBinaryTimestampToTimestampScanner struct{ location *time.Location }

func ( *scanPlanBinaryTimestampToTimestampScanner) ( []byte,  any) error {
	 := ().(TimestampScanner)

	if  == nil {
		return .ScanTimestamp(Timestamp{})
	}

	if len() != 8 {
		return fmt.Errorf("invalid length for timestamp: %v", len())
	}

	var  Timestamp
	 := int64(binary.BigEndian.Uint64())

	switch  {
	case infinityMicrosecondOffset:
		 = Timestamp{Valid: true, InfinityModifier: Infinity}
	case negativeInfinityMicrosecondOffset:
		 = Timestamp{Valid: true, InfinityModifier: -Infinity}
	default:
		 := time.Unix(
			microsecFromUnixEpochToY2K/1_000_000+/1_000_000,
			(microsecFromUnixEpochToY2K%1_000_000*1_000)+(%1_000_000*1000),
		).UTC()
		if .location != nil {
			 = time.Date(.Year(), .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), .location)
		}
		 = Timestamp{Time: , Valid: true}
	}

	return .ScanTimestamp()
}

type scanPlanTextTimestampToTimestampScanner struct{ location *time.Location }

func ( *scanPlanTextTimestampToTimestampScanner) ( []byte,  any) error {
	 := ().(TimestampScanner)

	if  == nil {
		return .ScanTimestamp(Timestamp{})
	}

	var  Timestamp
	 := string()
	switch  {
	case "infinity":
		 = Timestamp{Valid: true, InfinityModifier: Infinity}
	case "-infinity":
		 = Timestamp{Valid: true, InfinityModifier: -Infinity}
	default:
		 := false
		if strings.HasSuffix(, " BC") {
			 = [:len()-3]
			 = true
		}
		,  := time.Parse(pgTimestampFormat, )
		if  != nil {
			return 
		}

		if  {
			 := -.Year() + 1
			 = time.Date(, .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), .Location())
		}

		if .location != nil {
			 = time.Date(.Year(), .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), .location)
		}

		 = Timestamp{Time: , Valid: true}
	}

	return .ScanTimestamp()
}

func ( *TimestampCodec) ( *Map,  uint32,  int16,  []byte) (driver.Value, error) {
	if  == nil {
		return nil, nil
	}

	var  Timestamp
	 := codecScan(, , , , , &)
	if  != nil {
		return nil, 
	}

	if .InfinityModifier != Finite {
		return .InfinityModifier.String(), nil
	}

	return .Time, nil
}

func ( *TimestampCodec) ( *Map,  uint32,  int16,  []byte) (any, error) {
	if  == nil {
		return nil, nil
	}

	var  Timestamp
	 := codecScan(, , , , , &)
	if  != nil {
		return nil, 
	}

	if .InfinityModifier != Finite {
		return .InfinityModifier, nil
	}

	return .Time, nil
}