package model

import (
	"database/sql/driver"
	"encoding/json"
	"strconv"
	"strings"
	"time"
)

type ID uint64

func (id ID) String() string {
	return strconv.FormatUint(uint64(id), 10)
}

// IDSlice is a []ID but stores as a CSV in the database.
type IDSlice []ID

func (s IDSlice) Value() (driver.Value, error) {
	if len(s) == 0 {
		return "", nil
	}
	b := strings.Builder{}
	for i, id := range s {
		if i > 0 {
			b.WriteString(",")
		}
		b.WriteString(id.String())
	}
	return b.String(), nil
}

func (s *IDSlice) Scan(src interface{}) error {
	var data string
	switch v := src.(type) {
	case string:
		data = v
	case []byte:
		data = string(v)
	default:
	}
	if len(data) == 0 {
		*s = []ID{}
		return nil
	}
	xs := strings.Split(data, ",")
	res := make([]ID, len(xs))
	for i, x := range xs {
		id, err := strconv.ParseUint(x, 10, 64)
		if err != nil {
			return err
		}
		res[i] = ID(id)
	}
	*s = res
	return nil
}

type Timestamp int64

func (ts Timestamp) Time() time.Time { return time.Unix(int64(ts), 0) }

func (s *Timestamp) Scan(src interface{}) error {
	switch v := src.(type) {
	case time.Time:
		*s = Timestamp(v.Unix())
	case int64:
		*s = Timestamp(v)
	case int:
		*s = Timestamp(v)
	default:
	}
	return nil
}

// StringSlice is a []string but stores as a JSON array in the database.
type StringSlice []string

func (s StringSlice) Value() (driver.Value, error) {
	if len(s) == 0 {
		return []byte("[]"), nil
	}
	return json.Marshal(s)
}

func (s *StringSlice) Scan(src interface{}) error {
	var data []byte
	switch v := src.(type) {
	case string:
		data = []byte(v)
	case []byte:
		data = v
	default:
	}
	if len(data) == 0 {
		*s = []string{}
		return nil
	}
	return json.Unmarshal(data, s)
}

// StringSlice is a []string but stores as a CSV in the database.
// NOTE: It will not escape string while encoding! This is not a good idea, but it's a lot easier to use.
type StringCSV []string

func (s StringCSV) Value() (driver.Value, error) {
	return strings.Join(s, ","), nil
}

func (s *StringCSV) Scan(src interface{}) error {
	var data string
	switch v := src.(type) {
	case string:
		data = v
	case []byte:
		data = string(v)
	default:
	}
	if len(data) == 0 {
		*s = []string{}
		return nil
	}
	*s = strings.Split(data, ",")
	return nil
}

type User struct {
	ID     ID     `json:"id"`
	Email  string `json:"email"`
	Name   string `json:"name,omitempty"`
	Avatar string `json:"avatar,omitempty"`
}

func GetInternalUser() *User {
	return &User{
		ID:     0,
		Email:  "tcms@pingcap.com",
		Name:   "internal",
		Avatar: "",
	}
}

type UserIDAndEmail struct {
	ID    ID     `json:"id" db:"user_id"`
	Email string `json:"email" db:"user_email"`
}

type IDAndName struct {
	ID   ID     `json:"id" db:"id"`
	Name string `json:"name" db:"name"`
}

type CreatedBy struct {
	ID    ID     `json:"id" db:"creator"`
	Email string `json:"email" db:"creator_email"`
}

type CreatedAtAndUpdatedAt struct {
	CreatedAt time.Time `json:"createdAt" yaml:"createdAt" db:"created_at"`
	UpdatedAt time.Time `json:"updatedAt" yaml:"updatedAt" db:"updated_at"`
}

// Time but only include hour and minute.
type HourMinute string

func (t HourMinute) Time() (time.Time, error) {
	return time.Parse("15:04", string(t))
}

func (t HourMinute) BeforeOrEqual(rhs time.Time) bool {
	lhs, err := t.Time()
	if err != nil {
		return false
	}

	switch hour := lhs.Hour() - rhs.Hour(); {
	case hour > 0:
		return false
	case hour == 0:
		return lhs.Minute() <= rhs.Minute()
	default:
		return true
	}
}

func (t HourMinute) After(rhs time.Time) bool {
	return !t.BeforeOrEqual(rhs)
}
