package jump

import (
	"context"
	"fmt"
	"slices"
	"strings"
	"time"

	"github.com/tidbcloud/devops/pkg/infra/clients/oncallcli"

	privent "github.com/tidbcloud/devops/entity/privilege"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/log"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	timeutil "github.com/tidbcloud/devops/pkg/infra/utils/time"
	privapi "github.com/tidbcloud/devops/pkg/service/privilege/api"
)

// rotationCycle returns the current rotation cycle and privilege expiry time.
// Schedule query window: Friday 11:05 → next Friday 10:55 (±5min tolerance
// around the 11:00 rotation anchor). Privilege expiry: Friday 24:00 UTC+8.
func rotationCycle(now time.Time) (cycleStart, cycleEnd, privExpiry time.Time) {
	t := now.In(timeutil.ChinaTimeZone)
	weekday := t.Weekday()
	daysSinceFri := int(weekday-time.Friday+7) % 7
	lastFri := time.Date(t.Year(), t.Month(), t.Day()-daysSinceFri, 11, 5, 0, 0, timeutil.ChinaTimeZone)
	if lastFri.After(t) {
		lastFri = lastFri.AddDate(0, 0, -7)
	}
	cycleStart = lastFri
	cycleEnd = lastFri.AddDate(0, 0, 7).Add(-10 * time.Minute)
	privExpiry = time.Date(cycleEnd.Year(), cycleEnd.Month(), cycleEnd.Day(), 24, 0, 0, 0, timeutil.ChinaTimeZone)
	return
}

func (s *svc) SyncOncallerPrivileges(ctx context.Context) error {
	logger := log.FromContext(ctx)

	teams, err := s.onCallCli.GetOverview(ctx)
	if err != nil {
		return fmt.Errorf("get oncall overview failed: %w", err)
	}

	now := time.Now()
	curStart, curEnd, curExpiry := rotationCycle(now)
	nextStart, nextEnd, nextExpiry := rotationCycle(curEnd)

	windows := []struct {
		start, end time.Time
		expiry     time.Time
	}{
		{curStart, curEnd, curExpiry},
	}
	if now.After(curEnd.Add(-24 * time.Hour)) {
		windows = append(windows, struct {
			start, end time.Time
			expiry     time.Time
		}{nextStart, nextEnd, nextExpiry})
	}

	for _, w := range windows {
		oncallers, err := s.collectOncallers(ctx, teams, w.start.Unix(), w.end.Unix())
		if err != nil {
			logger.Error(err, "failed to collect oncallers", "start", w.start, "end", w.end)
			continue
		}
		if err := s.syncOncallers(ctx, oncallers, w.start, w.expiry); err != nil {
			logger.Error(err, "failed to sync oncaller privileges", "expiry", w.expiry)
		}
	}

	return nil
}

func (s *svc) collectOncallers(ctx context.Context, teams []oncallcli.OverviewTeam, startTs, endTs int64) ([]string, error) {
	seen := container.NewSet[string]()
	for _, team := range teams {
		resp, err := s.onCallCli.GetSchedule(ctx, oncallcli.GetScheduleParam{
			TeamID:    int(team.TeamID),
			StartTime: startTs,
			EndTime:   endTs,
		})
		if err != nil {
			return nil, fmt.Errorf("get schedule for team %s failed: %w", team.Name, err)
		}

		for _, row := range resp.Rows {
			if row.EngineerEmail != "" {
				seen.Insert(strings.ToLower(row.EngineerEmail))
			}
		}
	}

	return seen.Slice(), nil
}

func (s *svc) syncOncallers(ctx context.Context, oncallers []string, cycleStart, cycleExpiry time.Time) error {
	logger := log.FromContext(ctx)
	expiryTs := cycleExpiry.Unix()

	source := fmt.Sprintf("%s-%s-%s", privent.PrivilegeSourceOncallerAuto,
		cycleStart.Format("20060102"), cycleExpiry.Format("20060102"))

	curPrivs, _, err := s.privSvc.ListPrivileges(ctx, privapi.ListPrivilegesParam{
		PhaseNotIn: privent.PrivilegePhaseFinishedValues,
		SourceLike: pointer.Pointer(privent.PrivilegeSourceOncallerAuto + "%"),
	})
	if err != nil {
		return fmt.Errorf("list privileges failed: %w", err)
	}

	var missedOncallers []string
	for _, email := range oncallers {
		covered := slices.ContainsFunc(curPrivs, func(priv *privent.Privilege) bool {
			if priv.ExpiredAt < expiryTs {
				return false
			}
			return slices.ContainsFunc(priv.Bindings, func(b *privent.PrivilegeBinding) bool {
				return strings.EqualFold(b.User, email)
			})
		})
		if !covered {
			missedOncallers = append(missedOncallers, email)
		}
	}

	if len(missedOncallers) > 0 {
		_, err = s.privSvc.RequestPrivilege(ctx, privapi.RequestPrivilegeParam{
			Source:       source,
			Reason:       "According to the Oncall schedule, these colleagues need to request the Jump Service privilege in advance.",
			ExpiredAt:    expiryTs,
			Applicant:    consts.InternalVirtualEmail,
			BindingUsers: missedOncallers,
			Approval: &privapi.RequestApprovalPrivilegeParam{
				Scopes: []privent.ApprovalPrivilegeScope{
					privent.PrivilegeApprovalScopeJumpService,
				},
			},
		})
		if err != nil {
			return fmt.Errorf("request privilege for oncallers failed: %w", err)
		}
		logger.Info("created oncaller privileges", "source", source, "users", missedOncallers, "expired_at", expiryTs)
	}

	return nil
}
