package column

import (
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/ClickHouse/clickhouse-go/lib/binary"
)

type Column interface {
	Name() string
	CHType() string
	ScanType() reflect.Type
	Read(*binary.Decoder, bool) (interface{}, error)
	Write(*binary.Encoder, interface{}) error
	defaultValue() interface{}
	Depth() int
}

func Factory(name, chType string, timezone *time.Location) (Column, error) {
	switch chType {
	case "Int8":
		return &Int8{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[int8(0)],
			},
		}, nil
	case "Int16":
		return &Int16{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[int16(0)],
			},
		}, nil
	case "Int32":
		return &Int32{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[int32(0)],
			},
		}, nil
	case "Int64":
		return &Int64{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[int64(0)],
			},
		}, nil
	case "UInt8":
		return &UInt8{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[uint8(0)],
			},
		}, nil
	case "UInt16":
		return &UInt16{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[uint16(0)],
			},
		}, nil
	case "UInt32":
		return &UInt32{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[uint32(0)],
			},
		}, nil
	case "UInt64":
		return &UInt64{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[uint64(0)],
			},
		}, nil
	case "Float32":
		return &Float32{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[float32(0)],
			},
		}, nil
	case "Float64":
		return &Float64{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[float64(0)],
			},
		}, nil
	case "String":
		return &String{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[string("")],
			},
		}, nil
	case "UUID":
		return &UUID{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[string("")],
			},
		}, nil
	case "Date":
		_, offset := time.Unix(0, 0).In(timezone).Zone()
		return &Date{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[time.Time{}],
			},
			Timezone: timezone,
			offset:   int64(offset),
		}, nil
	case "IPv4":
		return &IPv4{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[IPv4{}],
			},
		}, nil
	case "IPv6":
		return &IPv6{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[IPv6{}],
			},
		}, nil
	}
	switch {
	case strings.HasPrefix(chType, "DateTime") && !strings.HasPrefix(chType, "DateTime64"):
		return &DateTime{
			base: base{
				name:    name,
				chType:  "DateTime",
				valueOf: columnBaseTypes[time.Time{}],
			},
			Timezone: timezone,
		}, nil
	case strings.HasPrefix(chType, "DateTime64"):
		return &DateTime64{
			base: base{
				name:    name,
				chType:  chType,
				valueOf: columnBaseTypes[time.Time{}],
			},
			Timezone: timezone,
		}, nil
	case strings.HasPrefix(chType, "Array"):
		return parseArray(name, chType, timezone)
	case strings.HasPrefix(chType, "Nullable"):
		return parseNullable(name, chType, timezone)
	case strings.HasPrefix(chType, "FixedString"):
		return parseFixedString(name, chType)
	case strings.HasPrefix(chType, "Enum8"), strings.HasPrefix(chType, "Enum16"):
		return parseEnum(name, chType)
	case strings.HasPrefix(chType, "Decimal"):
		return parseDecimal(name, chType)
	case strings.HasPrefix(chType, "SimpleAggregateFunction"):
		if nestedType, err := getNestedType(chType, "SimpleAggregateFunction"); err != nil {
			return nil, err
		} else {
			return Factory(name, nestedType, timezone)
		}
	case strings.HasPrefix(chType, "Tuple"):
		return parseTuple(name, chType, timezone)
	}
	return nil, fmt.Errorf("column: unhandled type %v", chType)
}

func getNestedType(chType string, wrapType string) (string, error) {
	prefixLen := len(wrapType) + 1
	suffixLen := 1

	if len(chType) > prefixLen+suffixLen {
		nested := strings.Split(chType[prefixLen:len(chType)-suffixLen], ",")
		if len(nested) == 2 {
			return strings.TrimSpace(nested[1]), nil
		}

		if len(nested) == 3 {
			return strings.TrimSpace(strings.Join(nested[1:], ",")), nil
		}
	}

	return "", fmt.Errorf("column: invalid %s type (%s)", wrapType, chType)
}
