package core

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"time"
)

type Actions map[string]json.RawMessage

func (a Actions) Value() (driver.Value, error) {
	return json.Marshal(a)
}

func (a *Actions) Scan(value interface{}) error {
	return json.Unmarshal([]byte(value.(string)), a)
}

type Item struct {
	Source  string  `json:"source"`
	Id      string  `json:"id"`
	Created int     `json:"created"`
	Active  bool    `json:"active"`
	Title   string  `json:"title"`
	Author  string  `json:"author"`
	Body    string  `json:"body"`
	Link    string  `json:"link"`
	Time    int     `json:"time"`
	Ttl     int     `json:"ttl"`
	Ttd     int     `json:"ttd"`
	Tts     int     `json:"tts"`
	Action  Actions `json:"action"`
}

func (item Item) TtlTime() time.Time {
	return time.Unix(int64(item.Created)+int64(item.Ttl), 0)
}

func (item Item) TtdTime() time.Time {
	return time.Unix(int64(item.Created)+int64(item.Ttd), 0)
}

func (item Item) TtsTime() time.Time {
	return time.Unix(int64(item.Created)+int64(item.Tts), 0)
}

func (item Item) Visible() bool {
	now := time.Now() // TODO pass this value in
	return item.Active && now.After(item.TtsTime())
}

// Whether an item that no longer appears in a fetch can be deleted.
func (item Item) Deletable(now time.Time) bool {
	if item.Ttl != 0 && item.TtlTime().After(now) {
		return false
	}
	if item.Ttd != 0 && item.TtdTime().Before(now) {
		return true
	}
	return !item.Active
}

func ItemsAreEqual(first Item, second Item) bool {
	// Hacky but easy to use
	return fmt.Sprintf("%#v", first) == fmt.Sprintf("%#v", second)
}

func FormatAsHeadline(item Item) string {
	title := item.Title
	if title == "" {
		title = item.Id
	}
	return title
}

func FormatAsJson(item Item) string {
	data, err := json.Marshal(item)
	if err != nil {
		log.Fatalf("error: failed to serialize %s/%s: %v", item.Source, item.Id, err)
	}
	return string(data)
}

func FormatAsShort(item Item) string {
	return fmt.Sprintf("%s/%s", item.Source, item.Id)
}

func FormatAs(format string) (func(item Item) string, error) {
	switch format {
	case "headlines":
		return FormatAsHeadline, nil
	case "json":
		return FormatAsJson, nil
	case "short":
		return FormatAsShort, nil
	default:
		return nil, fmt.Errorf("invalid format '%s'", format)
	}
}

var AvailableFormats = map[string]string{
	"headlines": "Only item titles",
	"json":      "Full item JSON",
	"short":     "Item source and id",
}

const hexDigits = "0123456789abcdef"

func RandomHex(n int) string {
	bytes := make([]byte, n)
	for i := range bytes {
		bytes[i] = hexDigits[rand.Intn(len(hexDigits))]
	}
	return string(bytes)
}