package autoshift

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/escalation-base/support-autoshift/pkg/infra/config"
	"github.com/tidbcloud/escalation-base/support-autoshift/pkg/infra/log"
	"github.com/tidbcloud/escalation-base/support-autoshift/pkg/service"
	jiraclimanager "github.com/tidbcloud/escalation-base/support-autoshift/pkg/service/autoshift/jiracli_manager"
	shiftmanager "github.com/tidbcloud/escalation-base/support-autoshift/pkg/service/autoshift/shift_manager"
	"go.uber.org/dig"
)

const (
	syncShiftInterval = time.Minute * 30
)

type ServiceParam struct {
	dig.In

	Conf   *config.RootConfig
	Logger logr.Logger
}

type ServiceResult struct {
	dig.Out

	API     AutoshiftServiceAPI
	Service service.Service `group:"service"`
}

type svc struct {
	conf   config.AutoshiftServiceConfig
	logger logr.Logger

	jiraClientManager jiraclimanager.JiraClientManager
	shiftManager      shiftmanager.ShiftManager
}

func NewService(p ServiceParam) (ServiceResult, error) {
	s := &svc{
		conf:   p.Conf.Services.Autoshift,
		logger: p.Logger,
	}

	jiraCliManagerParams := jiraclimanager.Params{
		Configs: s.conf.JiraProjects,
	}
	jiraClientManager, err := jiraclimanager.NewJiraCliManager(jiraCliManagerParams)
	if err != nil {
		return ServiceResult{API: s, Service: s}, err
	}
	s.jiraClientManager = jiraClientManager

	shiftManagerParams := shiftmanager.Params{
		Config: s.conf,
	}
	shiftmanager, err := shiftmanager.NewShiftManager(shiftManagerParams)
	if err != nil {
		return ServiceResult{API: s, Service: s}, err
	}
	s.shiftManager = shiftmanager

	return ServiceResult{API: s, Service: s}, nil
}

func (s *svc) Name() string {
	return "AutoshiftService"
}

func (s *svc) GetCurrentShifts(ctx context.Context) ([]string, error) {
	emails := []string{}

	shifts, err := s.shiftManager.GetShiftsAtSpecificTime(ctx, time.Now())
	if err != nil {
		return emails, err
	}

	for _, shift := range shifts {
		emails = append(emails, shift.Email)
	}
	return emails, nil
}

func (s *svc) Run(ctx context.Context) error {
	syncShiftTimer := time.NewTimer(time.Duration(s.conf.SyncIntervalMinutes) * time.Minute)
	s.logger.Info("set up sync interval", "sync_interval_minutes", s.conf.SyncIntervalMinutes)
	syncNr := 0
	defer syncShiftTimer.Stop()

	for {
		select {
		case <-ctx.Done():
			cause := context.Cause(ctx)
			s.logger.Info("exit because context canceled", "cause", cause)
			return nil
		case <-syncShiftTimer.C:
			syncNr++
			ctx := log.NewContext(ctx, s.logger, "sync_count", syncNr)

			s.syncShifts(ctx, "period")

			syncShiftTimer.Reset(time.Duration(s.conf.SyncIntervalMinutes) * time.Minute)
		}
	}
}

func (s *svc) syncShifts(ctx context.Context, caller string) {
	logger := log.FromContextOrDef(ctx, s.logger, "caller", caller)
	currentTime := time.Now()

	roles, err := s.shiftManager.GetInUseRoles(ctx)
	if err != nil {
		logger.Error(err, "get in use roles failed", "time", currentTime)
		return
	}

	currShifts, err := s.shiftManager.GetShiftsAtSpecificTime(ctx, currentTime)
	if err != nil {
		logger.Error(err, "get current shifts failed", "time", currentTime)
		return
	}

	for _, role := range roles {
		jiraCli, err := s.jiraClientManager.GetJiraCliWithProjectName(ctx, role.ProjectName)
		if err != nil {
			logger.Error(err, "get jira client failed", "projectName", role.ProjectName)
		}

		oldEmails, err := jiraCli.GetRoleActors(ctx, role.RoleID)
		if err != nil {
			logger.Error(err, "get role actors failed", "projectName", role.ProjectName, "roleName", role.Name, "roleID", role.RoleID)
			return
		} else {
			logger.Info("get role actors successfully", "projectName", role.ProjectName, "roleName", role.Name, "roleID", role.RoleID, "currActors", oldEmails)
		}

		// Hack: Sleep after rest api to avoid rate limit. Not elegant, need improve in the future.
		time.Sleep(time.Second)

		newEmails := make([]string, 0, len(oldEmails)+len(currShifts))

		for _, email := range oldEmails {
			shiftsForEmail, err := s.shiftManager.GetShiftsByEmail(ctx, email)
			if err != nil {
				logger.Error(err, "failed to call GetShiftsByEmail", "projectName", role.ProjectName, "email", email)
				return
			}
			// when len(shiftsForEmail) == 0, it means this email is not within our scope and should leave it intact.
			if len(shiftsForEmail) == 0 {
				newEmails = append(newEmails, email)
			}
		}

		for _, shift := range currShifts {
			if contains(shift.RoleUniqueIDs, role.RoleUniqueID) {
				newEmails = append(newEmails, shift.Email)
			}
		}

		err = jiraCli.UpdateRoleActors(ctx, role.RoleID, newEmails)
		if err != nil {
			logger.Error(err, "failed to update role actors", "role", role, "oldActors", oldEmails, "newActors", newEmails)
			return
		} else {
			logger.Info("update role sucessfully", "role", role, "oldActors", oldEmails, "newActors", newEmails)
		}

		// Hack: Sleep after rest api to avoid rate limit. Not elegant, need improve in the future.
		time.Sleep(time.Second)
	}
}

func contains(s []string, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}
