package service

import (
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/aisupport/services/oncall/db"
	"github.com/tidbcloud/aisupport/services/oncall/models"
	"gorm.io/gorm"
)

func normalizeSourceType(raw string) string {
	s := strings.ToLower(strings.TrimSpace(raw))
	if s == "cycle-generate" || s == "manual-edit" {
		return s
	}
	if strings.Contains(s, "cycle") || strings.Contains(s, "generate") || strings.Contains(s, "strategy") {
		return "cycle-generate"
	}
	return "manual-edit"
}

type OverlayApplyItem struct {
	TeamID        uint   `json:"team_id"`
	EngineerID    uint   `json:"engineer_id,omitempty"`
	EngineerEmail string `json:"engineer_email,omitempty"`
	StartTime     int64  `json:"start_time"`
	EndTime       int64  `json:"end_time"`
	Overlay       *int   `json:"overlay,omitempty"`
	OverlayMode   string `json:"overlay_mode,omitempty"` // fixed | max_plus_one
	IsBackup      bool   `json:"is_backup,omitempty"`
	SourceType    string `json:"source_type,omitempty"`
	Reason        string `json:"reason,omitempty"`
	PlanSignature string `json:"plan_signature,omitempty"`
}

func (s *ScheduleService) ApplyManualRangeOverlay(teamID uint, startTime, endTime int64, engineerEmails []string, isBackup bool, createdBy string) ([]map[string]interface{}, error) {
	if teamID == 0 {
		return nil, errors.New("missing team_id")
	}
	if startTime <= 0 || endTime <= 0 || endTime <= startTime {
		return nil, errors.New("invalid time range")
	}
	avail, err := s.GetTeamAvailableEngineers(teamID)
	if err != nil {
		return nil, err
	}
	availSet := make(map[string]struct{}, len(avail))
	for _, a := range avail {
		em := normalizeEmail(fmt.Sprintf("%v", a["engineer_email"]))
		if em != "" {
			availSet[em] = struct{}{}
		}
	}

	normalized := make([]string, 0, len(engineerEmails))
	seen := map[string]struct{}{}
	for i, e := range engineerEmails {
		em := normalizeEmail(e)
		if em == "" {
			continue
		}
		if _, ok := availSet[em]; !ok {
			return nil, fmt.Errorf("engineer_emails[%d] not in team: %s", i, em)
		}
		if _, ok := seen[em]; ok {
			continue
		}
		seen[em] = struct{}{}
		normalized = append(normalized, em)
	}

	var maxOverlay int
	if err := db.DB.Model(&models.ShiftInterval{}).
		Where("team_id = ? AND is_backup = ? AND end_time > ? AND start_time < ?", teamID, isBackup, startTime, endTime).
		Select("COALESCE(MAX(overlay), 0)").
		Scan(&maxOverlay).Error; err != nil {
		return nil, err
	}
	if maxOverlay < 0 {
		maxOverlay = 0
	}
	overlay := maxOverlay + 1
	itemCap := len(normalized)
	if itemCap == 0 {
		itemCap = 1
	}
	items := make([]OverlayApplyItem, 0, itemCap)
	if len(normalized) == 0 {
		o := overlay
		items = append(items, OverlayApplyItem{
			TeamID:      teamID,
			StartTime:   startTime,
			EndTime:     endTime,
			Overlay:     &o,
			OverlayMode: "fixed",
			IsBackup:    isBackup,
			SourceType:  "manual-edit",
			Reason:      "manual no-oncall segment",
		})
		return s.ApplyOverlayItems(items, createdBy)
	}
	for _, em := range normalized {
		o := overlay
		items = append(items, OverlayApplyItem{
			TeamID:        teamID,
			EngineerEmail: em,
			StartTime:     startTime,
			EndTime:       endTime,
			Overlay:       &o,
			OverlayMode:   "fixed",
			IsBackup:      isBackup,
			SourceType:    "manual-edit",
		})
	}
	return s.ApplyOverlayItems(items, createdBy)
}

func (s *ScheduleService) ApplyOverlayItems(items []OverlayApplyItem, createdBy string) ([]map[string]interface{}, error) {
	if len(items) == 0 {
		return nil, errors.New("items is required")
	}

	out := make([]map[string]interface{}, 0, len(items))
	err := db.DB.Transaction(func(tx *gorm.DB) error {
		for i, it := range items {
			if it.TeamID == 0 {
				return fmt.Errorf("items[%d]: missing team_id", i)
			}
			if it.StartTime <= 0 || it.EndTime <= 0 || it.EndTime <= it.StartTime {
				return fmt.Errorf("items[%d]: invalid time range", i)
			}

			engID, err := s.resolveEngineerID(it.EngineerID, it.EngineerEmail, false)
			if err != nil {
				if strings.TrimSpace(it.EngineerEmail) == "" && it.EngineerID == 0 {
					engID = 0
				} else {
					return fmt.Errorf("items[%d]: %w", i, err)
				}
			}

			sourceType := strings.ToLower(strings.TrimSpace(it.SourceType))
			mode := strings.ToLower(strings.TrimSpace(it.OverlayMode))

			// Centralized overlay policy by source type:
			// - cycle-generate: baseline schedule, always fixed overlay=0
			// - manual-edit: always stack on top (max_plus_one)
			switch sourceType {
			case "cycle-generate":
				mode = "fixed"
				v := 0
				it.Overlay = &v
			case "manual-edit":
				if it.Overlay != nil {
					mode = "fixed"
				} else {
					mode = "max_plus_one"
				}
			}

			if mode == "" {
				if it.Overlay != nil {
					mode = "fixed"
				} else {
					mode = "max_plus_one"
				}
			}

			overlay := 0
			switch mode {
			case "fixed":
				if it.Overlay != nil {
					overlay = *it.Overlay
				}
				if overlay < 0 {
					return fmt.Errorf("items[%d]: overlay must be >= 0", i)
				}
			case "max_plus_one":
				var maxOverlay int
				if err := tx.Model(&models.ShiftInterval{}).
					Where("team_id = ? AND is_backup = ? AND end_time > ? AND start_time < ?", it.TeamID, it.IsBackup, it.StartTime, it.EndTime).
					Select("COALESCE(MAX(overlay), 0)").
					Scan(&maxOverlay).Error; err != nil {
					return err
				}
				if maxOverlay < 0 {
					maxOverlay = 0
				}
				overlay = maxOverlay + 1
			default:
				return fmt.Errorf("items[%d]: unsupported overlay_mode=%s", i, mode)
			}

			sourceTypeOut := normalizeSourceType(it.SourceType)
			item := models.ShiftInterval{
				TeamID:     it.TeamID,
				EngineerID: engID,
				StartTime:  it.StartTime,
				EndTime:    it.EndTime,
				Overlay:    overlay,
				IsBackup:   it.IsBackup,
				SourceType: sourceTypeOut,
				IsOverride: overlay > 0,
				CreatedBy:  strings.TrimSpace(createdBy),
			}
			if err := tx.Create(&item).Error; err != nil {
				return err
			}
			out = append(out, map[string]interface{}{
				"schedule_id": item.ID,
				"team_id":     item.TeamID,
				"engineer_id": item.EngineerID,
				"start_time":  item.StartTime,
				"end_time":    item.EndTime,
				"overlay":     item.Overlay,
				"is_backup":   item.IsBackup,
				"source_type": item.SourceType,
			})
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}

// RemoveOverlayRange deletes manual overrides (overlay>0) for a team/lane that
// intersect [startTime, endTime), reverting that span to the layer baseline.
func (s *ScheduleService) RemoveOverlayRange(teamID uint, startTime, endTime int64, isBackup bool) (int64, error) {
	if teamID == 0 {
		return 0, errors.New("missing team_id")
	}
	if startTime <= 0 || endTime <= 0 || endTime <= startTime {
		return 0, errors.New("invalid time range")
	}
	res := db.DB.Where("team_id = ? AND is_backup = ? AND overlay > 0 AND end_time > ? AND start_time < ?",
		teamID, isBackup, startTime, endTime).Delete(&models.ShiftInterval{})
	if res.Error != nil {
		return 0, res.Error
	}
	return res.RowsAffected, nil
}
