// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package core

import (
	"context"
	"errors"
	"time"

	"github.com/gosimple/slug"
	"github.com/robfig/cron"
)

var (
	errCronExprInvalid   = errors.New("Invalid Cronjob Expression")
	errCronNameInvalid   = errors.New("Invalid Cronjob Name")
	errCronBranchInvalid = errors.New("Invalid Cronjob Branch")
)

type (
	// Cron defines a cron job.
	Cron struct {
		ID       int64  `json:"id"`
		RepoID   int64  `json:"repo_id"`
		Name     string `json:"name"`
		Expr     string `json:"expr"`
		Next     int64  `json:"next"`
		Prev     int64  `json:"prev"`
		Event    string `json:"event"`
		Branch   string `json:"branch"`
		Target   string `json:"target,omitempty"`
		Disabled bool   `json:"disabled"`
		Created  int64  `json:"created"`
		Updated  int64  `json:"updated"`
		Version  int64  `json:"version"`
	}

	// CronStore persists cron information to storage.
	CronStore interface {
		// List returns a cron list from the datastore.
		List(context.Context, int64) ([]*Cron, error)

		// Ready returns a cron list from the datastore ready for execution.
		Ready(context.Context, int64) ([]*Cron, error)

		// Find returns a cron job from the datastore.
		Find(context.Context, int64) (*Cron, error)

		// FindName returns a cron job from the datastore.
		FindName(context.Context, int64, string) (*Cron, error)

		// Create persists a new cron job to the datastore.
		Create(context.Context, *Cron) error

		// Update persists an updated cron job to the datastore.
		Update(context.Context, *Cron) error

		// Delete deletes a cron job from the datastore.
		Delete(context.Context, *Cron) error
	}
)

// Validate validates the required fields and formats.
func (c *Cron) Validate() error {
	_, err := cron.Parse(c.Expr)
	if err != nil {
		return errCronExprInvalid
	}
	switch {
	case c.Name == "":
		return errCronNameInvalid
	case c.Name != slug.Make(c.Name):
		return errCronNameInvalid
	case c.Branch == "":
		return errCronBranchInvalid
	default:
		return nil
	}
}

// SetExpr sets the cron expression name and updates
// the next execution date.
func (c *Cron) SetExpr(expr string) error {
	_, err := cron.Parse(expr)
	if err != nil {
		return errCronExprInvalid
	}
	c.Expr = expr
	return c.Update()
}

// SetName sets the cronjob name.
func (c *Cron) SetName(name string) {
	c.Name = slug.Make(name)
}

// Update updates the next Cron execution date.
func (c *Cron) Update() error {
	sched, err := cron.Parse(c.Expr)
	if err != nil {
		return err
	}
	c.Next = sched.Next(time.Now()).Unix()
	return nil
}
