package shiftmanager

import (
	"context"
	"fmt"
	"regexp"
	"strconv"
	"time"

	"github.com/tidbcloud/escalation-base/support-autoshift/pkg/infra/config"
)

type Params struct {
	Config config.AutoshiftServiceConfig
}

type manager struct {
	shifts  []Shift
	roles   []Role
	timeLoc *time.Location
}

func NewShiftManager(p Params) (ShiftManager, error) {
	m := &manager{
		roles:  []Role{},
		shifts: []Shift{},
	}

	loc, err := time.LoadLocation(p.Config.Timezone)
	if err != nil {
		return m, err
	}
	m.timeLoc = loc

	for _, confRole := range p.Config.Roles {
		role := Role{
			RoleUniqueID: confRole.RoleUniqueID,
			RoleID:       confRole.RoleID,
			ProjectName:  confRole.ProjectName,
			Name:         confRole.Name,
		}
		m.roles = append(m.roles, role)
	}

	for _, confShift := range p.Config.Shifts {
		shift := Shift{
			Email:         confShift.OnCallerEmail,
			Days:          confShift.OnCallDays,
			RoleUniqueIDs: confShift.RoleUniqueIDs,
			Roles:         []Role{},
		}
		startHour, endHour, err := parseTimeRange(confShift.OnCallHours)
		if err != nil {
			return m, fmt.Errorf("fail to parse time range %s: %e", confShift.OnCallHours, err)
		}
		shift.StartHour, shift.EndHour = startHour, endHour
		for _, roleUniqueID := range confShift.RoleUniqueIDs {
			role, err := m.findRoleWithRoleUniqueID(roleUniqueID)
			// TODO: add a logger for error here
			if err == nil {
				shift.Roles = append(shift.Roles, role)
			}
		}
		m.shifts = append(m.shifts, shift)
	}
	return m, nil
}

func (m *manager) GetShiftsByEmail(ctx context.Context, email string) ([]Shift, error) {
	shifts := make([]Shift, 0, 1)
	for _, shift := range m.shifts {
		if shift.Email == email {
			shifts = append(shifts, shift)
		}
	}

	return shifts, nil
}

// Current not okay
func (m *manager) GetRolesByShiftID(ctx context.Context, shiftID uint64) ([]Role, error) {
	return []Role{}, nil
}

func (m *manager) GetAllRoles(ctx context.Context) ([]Role, error) {
	return m.roles, nil
}

func (m *manager) GetInUseRoles(ctx context.Context) ([]Role, error) {
	inUseRoles := []Role{}
	uIDSet := make(map[string]struct{})
	for _, shift := range m.shifts {
		for _, role := range shift.Roles {
			if _, ok := uIDSet[role.RoleUniqueID]; !ok {
				inUseRoles = append(inUseRoles, role)
				uIDSet[role.RoleUniqueID] = struct{}{}
			}
		}
	}
	return inUseRoles, nil
}

func (m *manager) GetRoleWithRoleUniqueID(ctx context.Context, uniqueID string) ([]Role, error) {
	return m.roles, nil
}

func (m *manager) BatchGetRolesWithRoleUniqueID(ctx context.Context, ids []string) ([]Role, error) {
	return m.roles, nil
}

// map roleUniqueID to shifts
func (m *manager) GetRoleReverseMap(ctx context.Context, ids []string) (map[string][]Shift, error) {
	reverseMap := make(map[string][]Shift)
	return reverseMap, nil
}

func (m *manager) GetShiftsAtSpecificTime(ctx context.Context, inputTime time.Time) ([]Shift, error) {
	returnedShifts := []Shift{}
	transferredTime := inputTime.In(m.timeLoc)
	for _, shift := range m.shifts {
		day := transferredTime.Weekday().String()
		if !contains(shift.Days, day) {
			continue
		}
		hour := transferredTime.Hour()
		if hour >= shift.StartHour && hour < shift.EndHour {
			returnedShifts = append(returnedShifts, shift)
		}
	}
	return returnedShifts, nil
}

func (m *manager) findRoleWithRoleUniqueID(uniqueID string) (Role, error) {
	for _, role := range m.roles {
		if role.RoleUniqueID == uniqueID {
			return role, nil
		}
	}
	return Role{}, fmt.Errorf("cannot find role for unique id %s", uniqueID)
}

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

func parseTimeRange(timeRange string) (int, int, error) {
	var startHour, endHour int

	re := regexp.MustCompile("([0-9]+)-([0-9]+)")

	matches := re.FindStringSubmatch(timeRange)
	// matches should be [a-b, a, b]
	if len(matches) != 3 {
		return 0, 0, fmt.Errorf("cannot find matches in range")
	}

	startHour, err := strconv.Atoi(matches[1])
	if err != nil {
		return 0, 0, fmt.Errorf("cannot parse number \"%s\" : %e", matches[0], err)
	}
	if startHour < 0 || startHour > 23 {
		return 0, 0, fmt.Errorf("illegal hour value \"%s\"", matches[0])
	}

	endHour, err = strconv.Atoi(matches[2])
	if err != nil {
		return 0, 0, fmt.Errorf("cannot parse number \"%s\" : %e", matches[1], err)
	}
	if endHour < 0 || endHour > 23 {
		return 0, 0, fmt.Errorf("illegal hour value \"%s\"", matches[1])
	}
	if endHour <= startHour {
		return 0, 0, fmt.Errorf("end hour %s should be greater than start hour %s", matches[1], matches[0])
	}

	return startHour, endHour, nil
}
