package pttodo

import (
	"bytes"
	"encoding/json"
	"fmt"
	"testing"
	"time"
)

func TestJSONSchedule(t *testing.T) {
	cases := map[string]struct {
		input          string
		wantMarshalled string
	}{
		"cron": {
			input:          "* * * * *",
			wantMarshalled: `"* * * * *"`,
		},
		"int": {
			input:          "123",
			wantMarshalled: `"123"`,
		},
		"duration": {
			input:          "1h",
			wantMarshalled: `"1h"`,
		},
	}

	for name, d := range cases {
		c := d
		t.Run(name, func(t *testing.T) {
			gotMarshalled, err := json.Marshal(Schedule(c.input))
			if err != nil {
				t.Fatal(err)
			}
			if !bytes.Equal([]byte(c.wantMarshalled), gotMarshalled) {
				t.Fatal(c.wantMarshalled, gotMarshalled)
			}
			var got Schedule
			if err := json.Unmarshal(gotMarshalled, &got); err != nil {
				t.Fatal(err)
			}
			if string(got) != c.input {
				t.Fatal(got)
			}
		})
	}
}

func TestSchedulerFactory(t *testing.T) {
	start := time.Date(2000, 1, 1, 1, 1, 1, 1, time.UTC)
	someDay := time.Date(2001, 2, 3, 0, 0, 0, 0, time.UTC)
	someHour := time.Date(2001, 2, 3, 15, 0, 0, 0, time.UTC)
	cases := map[string]struct {
		input string
		want  interface{}
		next  time.Time
	}{
		"ezdate with hour": {
			input: `2000-01-03T15`,
			want:  scheduleEZDate(`2000-01-03T15`),
			next:  someHour,
		},
		"long dur": {
			input: `2h1m`,
			want:  scheduleDuration("2h1m"),
			next:  start.Add(time.Hour * 2).Add(time.Minute),
		},
		"simple dur": {
			input: `1m`,
			want:  scheduleDuration("1m"),
			next:  start.Add(time.Minute),
		},
		"little ts": {
			input: `1`,
			want:  scheduleDue(1),
			next:  time.Unix(1, 0),
		},
		"never": {
			input: ``,
			want:  scheduleNever{},
			next:  never,
		},
		"always cron": {
			input: `* * * * *`,
			want:  scheduleCron(`* * * * *`),
			next:  start.Add(time.Duration(-1*start.Nanosecond() + -1*start.Second())).Add(time.Minute),
		},
		"fifth minute cron": {
			input: `5 * * * *`,
			want:  scheduleCron(`5 * * * *`),
			next:  start.Add(time.Duration(-1*start.Nanosecond() + -1*start.Minute() + -1*start.Second())).Add(5 * time.Minute),
		},
		"ezdate": {
			input: `2000-01-03`,
			want:  scheduleEZDate(`2000-01-03`),
			next:  someDay,
		},
	}

	for name, d := range cases {
		c := d
		t.Run(name, func(t *testing.T) {
			got := schedulerFactory(c.input)
			if fmt.Sprintf("%T", c.want) != fmt.Sprintf("%T", got) {
				t.Fatalf("want type %T, got %T", c.want, got)
			}
			if fmt.Sprint(c.want) != fmt.Sprint(got) {
				t.Fatalf("want %+v, got %+v", c.want, got)
			}
			next, _ := got.next(start)
			if next.Sub(c.next) > time.Second {
				t.Fatalf("want next %+v, got %+v", c.next, next)
			}
		})
	}

	t.Run("zero ts", func(t *testing.T) {
		got := schedulerFactory("0")
		if fmt.Sprintf("%T", scheduleDue(0)) != fmt.Sprintf("%T", got) {
			t.Fatalf("want type %T, got %T", scheduleDue(0), got)
		}
		if fmt.Sprint(scheduleDue(0)) != fmt.Sprint(got) {
			t.Fatalf("want %+v, got %+v", scheduleDue(0), got)
		}
		next, _ := got.next(start)
		if now := time.Now(); next.Sub(now) > time.Second {
			t.Fatalf("want next %+v, got %+v", now, next)
		}
	})
}
