package pgtype

import (
	
	
	
	
	
	

	
)

const pgTimestamptzHourFormat = "2006-01-02 15:04:05.999999999Z07"
const pgTimestamptzMinuteFormat = "2006-01-02 15:04:05.999999999Z07:00"
const pgTimestamptzSecondFormat = "2006-01-02 15:04:05.999999999Z07:00:00"
const microsecFromUnixEpochToY2K = 946684800 * 1000000

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
}

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

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
}

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

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

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{}
		}
	case TextFormatCode:
		switch .(type) {
		case TimestamptzScanner:
			return scanPlanTextTimestamptzToTimestamptzScanner{}
		}
	}

	return nil
}

type scanPlanBinaryTimestamptzToTimestamptzScanner struct{}

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

	return .ScanTimestamptz()
}

type scanPlanTextTimestamptzToTimestamptzScanner struct{}

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

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