package pgtype

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

type TextScanner interface {
	ScanText(v Text) error
}

type TextValuer interface {
	TextValue() (Text, error)
}

type Text struct {
	String string
	Valid  bool
}

func (t *Text) ScanText(v Text) error {
	*t = v
	return nil
}

func (t Text) TextValue() (Text, error) {
	return t, nil
}

// Scan implements the database/sql Scanner interface.
func (dst *Text) Scan(src any) error {
	if src == nil {
		*dst = Text{}
		return nil
	}

	switch src := src.(type) {
	case string:
		*dst = Text{String: src, Valid: true}
		return nil
	case []byte:
		*dst = Text{String: string(src), Valid: true}
		return nil
	}

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

// Value implements the database/sql/driver Valuer interface.
func (src Text) Value() (driver.Value, error) {
	if !src.Valid {
		return nil, nil
	}
	return src.String, nil
}

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

	return json.Marshal(src.String)
}

func (dst *Text) UnmarshalJSON(b []byte) error {
	var s *string
	err := json.Unmarshal(b, &s)
	if err != nil {
		return err
	}

	if s == nil {
		*dst = Text{}
	} else {
		*dst = Text{String: *s, Valid: true}
	}

	return nil
}

type TextCodec struct{}

func (TextCodec) FormatSupported(format int16) bool {
	return format == TextFormatCode || format == BinaryFormatCode
}

func (TextCodec) PreferredFormat() int16 {
	return TextFormatCode
}

func (TextCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
	switch format {
	case TextFormatCode, BinaryFormatCode:
		switch value.(type) {
		case string:
			return encodePlanTextCodecString{}
		case []byte:
			return encodePlanTextCodecByteSlice{}
		case TextValuer:
			return encodePlanTextCodecTextValuer{}
		}
	}

	return nil
}

type encodePlanTextCodecString struct{}

func (encodePlanTextCodecString) Encode(value any, buf []byte) (newBuf []byte, err error) {
	s := value.(string)
	buf = append(buf, s...)
	return buf, nil
}

type encodePlanTextCodecByteSlice struct{}

func (encodePlanTextCodecByteSlice) Encode(value any, buf []byte) (newBuf []byte, err error) {
	s := value.([]byte)
	buf = append(buf, s...)
	return buf, nil
}

type encodePlanTextCodecStringer struct{}

func (encodePlanTextCodecStringer) Encode(value any, buf []byte) (newBuf []byte, err error) {
	s := value.(fmt.Stringer)
	buf = append(buf, s.String()...)
	return buf, nil
}

type encodePlanTextCodecTextValuer struct{}

func (encodePlanTextCodecTextValuer) Encode(value any, buf []byte) (newBuf []byte, err error) {
	text, err := value.(TextValuer).TextValue()
	if err != nil {
		return nil, err
	}

	if !text.Valid {
		return nil, nil
	}

	buf = append(buf, text.String...)
	return buf, nil
}

func (TextCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan {

	switch format {
	case TextFormatCode, BinaryFormatCode:
		switch target.(type) {
		case *string:
			return scanPlanTextAnyToString{}
		case *[]byte:
			return scanPlanAnyToNewByteSlice{}
		case BytesScanner:
			return scanPlanAnyToByteScanner{}
		case TextScanner:
			return scanPlanTextAnyToTextScanner{}
		}
	}

	return nil
}

func (c TextCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) {
	return c.DecodeValue(m, oid, format, src)
}

func (c TextCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) {
	if src == nil {
		return nil, nil
	}

	return string(src), nil
}

type scanPlanTextAnyToString struct{}

func (scanPlanTextAnyToString) Scan(src []byte, dst any) error {
	if src == nil {
		return fmt.Errorf("cannot scan NULL into %T", dst)
	}

	p := (dst).(*string)
	*p = string(src)

	return nil
}

type scanPlanAnyToNewByteSlice struct{}

func (scanPlanAnyToNewByteSlice) Scan(src []byte, dst any) error {
	p := (dst).(*[]byte)
	if src == nil {
		*p = nil
	} else {
		*p = make([]byte, len(src))
		copy(*p, src)
	}

	return nil
}

type scanPlanAnyToByteScanner struct{}

func (scanPlanAnyToByteScanner) Scan(src []byte, dst any) error {
	p := (dst).(BytesScanner)
	return p.ScanBytes(src)
}

type scanPlanTextAnyToTextScanner struct{}

func (scanPlanTextAnyToTextScanner) Scan(src []byte, dst any) error {
	scanner := (dst).(TextScanner)

	if src == nil {
		return scanner.ScanText(Text{})
	}

	return scanner.ScanText(Text{String: string(src), Valid: true})
}