package pgtype

import (
	
	
	
	
	
	

	
)

const (
	pgTimestamptzHourFormat    = "2006-01-02 15:04:05.999999999Z07"
	pgTimestamptzMinuteFormat  = "2006-01-02 15:04:05.999999999Z07:00"
	pgTimestamptzSecondFormat  = "2006-01-02 15:04:05.999999999Z07:00:00"
	microsecFromUnixEpochToY2K = 946_684_800 * 1_000_000
)

const (
	negativeInfinityMicrosecondOffset = -9223372036854775808
	infinityMicrosecondOffset         = 9223372036854775807
)

type TimestamptzScanner interface {
	ScanTimestamptz(v Timestamptz) error
}

type TimestamptzValuer interface {
	TimestamptzValue() (Timestamptz, error)
}

// Timestamptz represents the PostgreSQL timestamptz type.
type Timestamptz struct {
	Time             time.Time
	InfinityModifier InfinityModifier
	Valid            bool
}

// ScanTimestamptz implements the [TimestamptzScanner] interface.
func ( *Timestamptz) ( Timestamptz) error {
	* = 
	return nil
}

// TimestamptzValue implements the [TimestamptzValuer] interface.
func ( Timestamptz) () (Timestamptz, error) {
	return , nil
}

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

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

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

// Value implements the [database/sql/driver.Valuer] interface.
func ( Timestamptz) () (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 ( Timestamptz) () ([]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()
}

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

	if  == nil {
		* = Timestamptz{}
		return nil
	}

	switch * {
	case "infinity":
		* = Timestamptz{Valid: true, InfinityModifier: Infinity}
	case "-infinity":
		* = Timestamptz{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 
		}

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

	return nil
}

type TimestamptzCodec struct {
	// ScanLocation is the location to return scanned timestamptz values in. This does not change the instant in time that
	// the timestamptz represents.
	ScanLocation *time.Location
}

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

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

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

	switch  {
	case BinaryFormatCode:
		return encodePlanTimestamptzCodecBinary{}
	case TextFormatCode:
		return encodePlanTimestamptzCodecText{}
	}

	return nil
}

type encodePlanTimestamptzCodecBinary struct{}

func (encodePlanTimestamptzCodecBinary) ( any,  []byte) ( []byte,  error) {
	,  := .(TimestamptzValuer).TimestamptzValue()
	if  != nil {
		return nil, 
	}

	if !.Valid {
		return nil, nil
	}

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

	 = pgio.AppendInt64(, )

	return , nil
}

type encodePlanTimestamptzCodecText struct{}

func (encodePlanTimestamptzCodecText) ( any,  []byte) ( []byte,  error) {
	,  := .(TimestamptzValuer).TimestamptzValue()
	if  != nil {
		return nil, 
	}

	if !.Valid {
		return nil, nil
	}

	var  string

	switch .InfinityModifier {
	case Finite:

		 := .Time.UTC().Truncate(time.Microsecond)

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

		 = .Format(pgTimestamptzSecondFormat)

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

	 = append(, ...)

	return , nil
}

func ( *TimestamptzCodec) ( *Map,  uint32,  int16,  any) ScanPlan {
	switch  {
	case BinaryFormatCode:
		switch .(type) {
		case TimestamptzScanner:
			return &scanPlanBinaryTimestamptzToTimestamptzScanner{location: .ScanLocation}
		}
	case TextFormatCode:
		switch .(type) {
		case TimestamptzScanner:
			return &scanPlanTextTimestamptzToTimestamptzScanner{location: .ScanLocation}
		}
	}

	return nil
}

type scanPlanBinaryTimestamptzToTimestamptzScanner struct{ location *time.Location }

func ( *scanPlanBinaryTimestamptzToTimestamptzScanner) ( []byte,  any) error {
	 := ().(TimestamptzScanner)

	if  == nil {
		return .ScanTimestamptz(Timestamptz{})
	}

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

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

	switch  {
	case infinityMicrosecondOffset:
		 = Timestamptz{Valid: true, InfinityModifier: Infinity}
	case negativeInfinityMicrosecondOffset:
		 = Timestamptz{Valid: true, InfinityModifier: -Infinity}
	default:
		 := time.Unix(
			microsecFromUnixEpochToY2K/1_000_000+/1_000_000,
			(microsecFromUnixEpochToY2K%1_000_000*1_000)+(%1_000_000*1_000),
		)
		if .location != nil {
			 = .In(.location)
		}
		 = Timestamptz{Time: , Valid: true}
	}

	return .ScanTimestamptz()
}

type scanPlanTextTimestamptzToTimestamptzScanner struct{ location *time.Location }

func ( *scanPlanTextTimestamptzToTimestamptzScanner) ( []byte,  any) error {
	 := ().(TimestamptzScanner)

	if  == nil {
		return .ScanTimestamptz(Timestamptz{})
	}

	var  Timestamptz
	 := string()
	switch  {
	case "infinity":
		 = Timestamptz{Valid: true, InfinityModifier: Infinity}
	case "-infinity":
		 = Timestamptz{Valid: true, InfinityModifier: -Infinity}
	default:
		 := false
		if strings.HasSuffix(, " BC") {
			 = [:len()-3]
			 = true
		}

		var  string
		if len() >= 9 && ([len()-9] == '-' || [len()-9] == '+') {
			 = pgTimestamptzSecondFormat
		} else if len() >= 6 && ([len()-6] == '-' || [len()-6] == '+') {
			 = pgTimestamptzMinuteFormat
		} else {
			 = pgTimestamptzHourFormat
		}

		,  := time.Parse(, )
		if  != nil {
			return 
		}

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

		if .location != nil {
			 = .In(.location)
		}

		 = Timestamptz{Time: , Valid: true}
	}

	return .ScanTimestamptz()
}

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

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

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

	return .Time, nil
}

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

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

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

	return .Time, nil
}