package pttodo

import (
	"encoding/json"
	"math"
	"regexp"
	"strconv"
	"time"

	cron "github.com/robfig/cron/v3"
)

type Schedule string

func (schedule Schedule) MarshalJSON() ([]byte, error) {
	return json.Marshal(string(schedule))
}

func (schedule *Schedule) UnmarshalJSON(b []byte) error {
	return json.Unmarshal(b, (*string)(schedule))
}

func (schedule Schedule) Next(t time.Time) (time.Time, error) {
	scheduler := schedulerFactory(string(schedule))
	return scheduler.next(t)
}

type scheduler interface {
	next(time.Time) (time.Time, error)
}

func schedulerFactory(s string) scheduler {
	if s == "" {
		return scheduleNever{}
	} else if scheduleDurationPattern.MatchString(s) {
		return scheduleDuration(s)
	} else if scheduleDuePattern.MatchString(s) {
		n, _ := strconv.Atoi(s)
		return scheduleDue(n)
	} else if scheduleEZDatePattern.MatchString(s) {
		return scheduleEZDate(s)
	}
	return scheduleCron(s)
}

// * * * * *
type scheduleCron string

func (c scheduleCron) next(t time.Time) (time.Time, error) {
	schedule, err := cron.ParseStandard(string(c))
	if err != nil {
		return time.Time{}, err
	}
	return schedule.Next(t), nil
}

// * * * * *
type scheduleNever struct{}

var never = time.Date(
	math.MaxInt32,
	1,
	1,
	1,
	1,
	1,
	1,
	time.UTC,
)

func (cron scheduleNever) next(time.Time) (time.Time, error) {
	return never, nil
}

// 4h5m
type scheduleDuration string

var scheduleDurationPattern = regexp.MustCompile(`^([0-9]+[a-z])+$`)

func (dur scheduleDuration) next(t time.Time) (time.Time, error) {
	d, err := time.ParseDuration(string(dur))
	return t.Add(d), err
}

// 123
type scheduleDue int64

var scheduleDuePattern = regexp.MustCompile(`^[0-9]+$`)

func (due scheduleDue) next(time.Time) (time.Time, error) {
	return TS(due).time(), nil
}

// 2022-01-01
type scheduleEZDate string

var scheduleEZDatePattern = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}(T[0-9]{2})?$`)

func (ezdate scheduleEZDate) next(time.Time) (time.Time, error) {
	if len(ezdate) == len("20xx-xx-xxTxx") {
		return time.ParseInLocation("2006-01-02T15", string(ezdate), time.Local)
	}
	return time.ParseInLocation("2006-01-02", string(ezdate), time.Local)
}
