package pgtype

import (
	
	
	
	
	
	

	
)

const pgTimestampFormat = "2006-01-02 15: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
}

func ( *Timestamp) ( Timestamp) error {
	* = 
	return nil
}

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
}

func ( Timestamp) () ([]byte, error) {
	if !.Valid {
		return []byte("null"), nil
	}

	var  string

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

	return json.Marshal()
}

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:
		// PostgreSQL uses ISO 8601 for to_json function and casting from a string to timestamptz
		,  := time.Parse(time.RFC3339Nano, *)
		if  != nil {
			return 
		}

		* = Timestamp{Time: , Valid: true}
	}

	return nil
}

type TimestampCodec struct{}

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()*1000000 + 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 discardTimeZone( 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{}
		}
	case TextFormatCode:
		switch .(type) {
		case TimestampScanner:
			return scanPlanTextTimestampToTimestampScanner{}
		}
	}

	return nil
}

type scanPlanBinaryTimestampToTimestampScanner struct{}

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/1000000+/1000000,
			(microsecFromUnixEpochToY2K%1000000*1000)+(%1000000*1000),
		).UTC()
		 = Timestamp{Time: , Valid: true}
	}

	return .ScanTimestamp()
}

type scanPlanTextTimestampToTimestampScanner struct{}

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())
		}

		 = 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
}