package steps

import (
	"context"
	"fmt"
	"time"

	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"go.uber.org/zap"
)

type periodicalTask struct {
	lifeTime time.Duration
	cron     time.Duration
	task     Step
}

func NewPeriodicalTask(lifeTime, cron time.Duration, task Step) Step {
	return &periodicalTask{
		lifeTime: lifeTime,
		cron:     cron,
		task:     task,
	}
}

func (s *periodicalTask) String() string {
	return fmt.Sprintf("periodicalTask: lifeTime=%+v, cron=%+v, task=%+v", s.lifeTime, s.cron, s.task)
}

func (s *periodicalTask) Execute(ctx context.Context) error {
	var (
		ticker    = time.NewTicker(s.cron)
		lifeTimer = time.NewTimer(s.lifeTime)
	)
	defer func() {
		ticker.Stop()
		lifeTimer.Stop()
	}()

	for {
		select {
		case <-lifeTimer.C:
			log.Info("periodicalTask safely stopped")
			return nil
		case <-ticker.C:
			if err := s.task.Execute(ctx); err != nil {
				log.Error("run periodicalTask failed", zap.Error(err), zap.Any("task", s.task))
				return errors.Trace(err)
			}
		}
	}
}

type loopTask struct {
	lifeTime time.Duration
	cron     time.Duration
	steps    []Step
	curr     int

	roundRobin bool
}

func NewLoopTask(lifeTime, cron time.Duration, roundRobin bool, steps ...Step) Step {
	return &loopTask{
		lifeTime:   lifeTime,
		cron:       cron,
		steps:      steps,
		curr:       0,
		roundRobin: roundRobin,
	}
}

func (s *loopTask) String() string {
	return fmt.Sprintf("loopTask: lifeTime=%+v, cron=%+v, roundRobin=%+v, steps=%+v", s.lifeTime, s.cron, s.roundRobin, s.steps)
}

func (s *loopTask) Execute(ctx context.Context) error {
	var (
		ticker    = time.NewTicker(s.cron)
		lifeTimer = time.NewTimer(s.lifeTime)
	)
	defer func() {
		ticker.Stop()
		lifeTimer.Stop()
	}()

	start := time.Now()
	for {
		select {
		case <-lifeTimer.C:
			log.Info("loopTask safely stopped", zap.Duration("elapsed", time.Since(start)))
			return nil
		case <-ticker.C:
			if err := s.steps[s.curr].Execute(ctx); err != nil {
				log.Error("run loopTask failed", zap.Error(err), zap.Any("task", s.steps[s.curr]))
				return errors.Trace(err)
			}

			s.curr = (s.curr + 1) % len(s.steps)
			if s.curr == 0 && !s.roundRobin {
				log.Info("loopTask without roundRobin, safely stopped.", zap.Duration("elapsed", time.Since(start)))
				return nil
			}
		}
	}
}

// delayTask delay execution of specified task for the specified time
type delayTask struct {
	delay time.Duration
	task  Step
}

func (d *delayTask) String() string {
	return fmt.Sprintf("delayTask deplay %s for step %s", d.delay, d.task)
}

func (d *delayTask) Execute(ctx context.Context) error {
	log.Info(fmt.Sprintf("Delay %s for task %s execution", d.delay, d.task))
	time.Sleep(d.delay)
	return d.task.Execute(ctx)
}

func NewDelayTask(delay time.Duration, task Step) Step {
	return &delayTask{delay: delay, task: task}
}

// ignoreError Ignore error of specified step
type ignoreError struct {
	task Step
}

func (i ignoreError) String() string {
	return fmt.Sprintf("Run task %s and ignore error", i.task)
}

func (i ignoreError) Execute(ctx context.Context) error {
	if err := i.task.Execute(ctx); err != nil {
		log.Warn(fmt.Sprintf("Task %s failed and error ignored. ERROR: %v", i.task, err))
	} else {
		log.Info(fmt.Sprintf("Task %s succeed", i.task))
	}
	return nil
}

func NewIgnoreError(task Step) Step {
	return &ignoreError{task: task}
}

func (b *Builder) IgnoreError(task Step) *Builder {
	b.steps = append(b.steps, NewIgnoreError(task))
	return b
}

// expectError Expect the step, and check step return per expectErrorReturned
type expectError struct {
	expectErrorReturned *bool
	task                Step
}

func (e expectError) String() string {
	return fmt.Sprintf("Run task %s and expectErrorReturned: %t from the task", e.task, *e.expectErrorReturned)
}

func (e expectError) Execute(ctx context.Context) error {
	err := e.task.Execute(ctx)
	if *e.expectErrorReturned {
		if err != nil {
			log.Info(fmt.Sprintf("Step %s expect error returned, and got error: %v", e.task, err))
		} else {
			return fmt.Errorf(fmt.Sprintf("Step %s expect error returned, but got nil", e.task))
		}
	} else {
		if err != nil {
			return fmt.Errorf(fmt.Sprintf("Step %s expect no error returned, but got error: %v", e.task, err))
		} else {
			log.Info(fmt.Sprintf("Step %s expect no error returned, and got nil", e.task))
		}
	}
	return nil
}

func NewExpectError(expectErrorReturned *bool, task Step) Step {
	return &expectError{expectErrorReturned: expectErrorReturned, task: task}
}

func (b *Builder) ExpectError(expectError *bool, task Step) *Builder {
	b.steps = append(b.steps, NewExpectError(expectError, task))
	return b
}
