package agentmodel

import (
	"sort"
	"strings"
	"time"

	agentshared "github.com/LoveLosita/smartflow/backend/agent/shared"
	"github.com/LoveLosita/smartflow/backend/model"
)

const (
	datetimeLayout = agentshared.MinuteLayout

	ScheduleRefineDefaultPlanMax        = 2
	ScheduleRefineDefaultExecuteMax     = 24
	ScheduleRefineDefaultPerTaskBudget  = 4
	ScheduleRefineDefaultReplanMax      = 2
	ScheduleRefineDefaultCompositeRetry = 2
	ScheduleRefineDefaultRepairReserve  = 1
)

const (
	defaultPlanMax        = ScheduleRefineDefaultPlanMax
	defaultExecuteMax     = ScheduleRefineDefaultExecuteMax
	defaultPerTaskBudget  = ScheduleRefineDefaultPerTaskBudget
	defaultReplanMax      = ScheduleRefineDefaultReplanMax
	defaultCompositeRetry = ScheduleRefineDefaultCompositeRetry
	defaultRepairReserve  = ScheduleRefineDefaultRepairReserve
)

// RefineContract 表示本轮微调意图契约。
type RefineContract struct {
	Intent            string            `json:"intent"`
	Strategy          string            `json:"strategy"`
	HardRequirements  []string          `json:"hard_requirements"`
	HardAssertions    []RefineAssertion `json:"hard_assertions,omitempty"`
	KeepRelativeOrder bool              `json:"keep_relative_order"`
	OrderScope        string            `json:"order_scope"`
}

// RefineAssertion 表示可由后端直接判定的结构化硬断言。
//
// 字段说明：
// 1. Metric：断言指标名，例如 source_move_ratio_percent；
// 2. Operator：比较操作符，支持 == / <= / >= / between；
// 3. Value/Min/Max：阈值；
// 4. Week/TargetWeek：可选周次上下文。
type RefineAssertion struct {
	Metric     string `json:"metric"`
	Operator   string `json:"operator"`
	Value      int    `json:"value,omitempty"`
	Min        int    `json:"min,omitempty"`
	Max        int    `json:"max,omitempty"`
	Week       int    `json:"week,omitempty"`
	TargetWeek int    `json:"target_week,omitempty"`
}

// HardCheckReport 表示终审硬校验结果。
type HardCheckReport struct {
	PhysicsPassed bool     `json:"physics_passed"`
	PhysicsIssues []string `json:"physics_issues,omitempty"`

	IntentPassed bool     `json:"intent_passed"`
	IntentReason string   `json:"intent_reason,omitempty"`
	IntentUnmet  []string `json:"intent_unmet,omitempty"`

	OrderPassed bool     `json:"order_passed"`
	OrderIssues []string `json:"order_issues,omitempty"`

	RepairTried bool `json:"repair_tried"`
}

// ReactRoundObservation 记录每轮 ReAct 的关键观察。
type ReactRoundObservation struct {
	Round         int            `json:"round"`
	GoalCheck     string         `json:"goal_check,omitempty"`
	Decision      string         `json:"decision,omitempty"`
	ToolName      string         `json:"tool_name,omitempty"`
	ToolParams    map[string]any `json:"tool_params,omitempty"`
	ToolSuccess   bool           `json:"tool_success"`
	ToolErrorCode string         `json:"tool_error_code,omitempty"`
	ToolResult    string         `json:"tool_result,omitempty"`
	Reflect       string         `json:"reflect,omitempty"`
}

// PlannerPlan 表示 Planner 生成的阶段执行计划。
type PlannerPlan struct {
	Summary string   `json:"summary"`
	Steps   []string `json:"steps,omitempty"`
}

// RefineSlicePlan 表示切片节点输出。
type RefineSlicePlan struct {
	WeekFilter      []int  `json:"week_filter,omitempty"`
	SourceDays      []int  `json:"source_days,omitempty"`
	TargetDays      []int  `json:"target_days,omitempty"`
	ExcludeSections []int  `json:"exclude_sections,omitempty"`
	Reason          string `json:"reason,omitempty"`
}

// RefineObjective 表示"可执行且可校验"的目标约束。
//
// 设计说明：
// 1. 由 contract/slice 从自然语言编译得到；
// 2. 执行阶段（done 收口）与终审阶段（hard_check）共用同一份约束；
// 3. 避免"执行逻辑与终审逻辑各说各话"。
type RefineObjective struct {
	Mode string `json:"mode,omitempty"` // none | move_all | move_ratio

	SourceWeeks []int `json:"source_weeks,omitempty"`
	TargetWeeks []int `json:"target_weeks,omitempty"`
	SourceDays  []int `json:"source_days,omitempty"`
	TargetDays  []int `json:"target_days,omitempty"`

	ExcludeSections []int `json:"exclude_sections,omitempty"`

	BaselineSourceTaskCount int `json:"baseline_source_task_count,omitempty"`
	RequiredMoveMin         int `json:"required_move_min,omitempty"`
	RequiredMoveMax         int `json:"required_move_max,omitempty"`

	Reason string `json:"reason,omitempty"`
}

// ScheduleRefineState 是连续微调图的统一状态。
type ScheduleRefineState struct {
	// 1) 请求上下文
	TraceID        string
	UserID         int
	ConversationID string
	UserMessage    string
	RequestNow     time.Time
	RequestNowText string

	// 2) 继承自预览快照的数据
	TaskClassIDs []int
	Constraints  []string
	// InitialHybridEntries 保存本轮微调开始前的基线，用于终审做"前后对比"。
	// 说明：
	// 1. 只读语义，不参与执行期改写；
	// 2. 终审只基于它判断"来源任务是否真正迁移到目标区域"。
	InitialHybridEntries []model.HybridScheduleEntry
	HybridEntries        []model.HybridScheduleEntry
	AllocatedItems       []model.TaskClassItem
	CandidatePlans       []model.UserWeekSchedule

	// 3) 本轮执行状态
	UserIntent string
	Contract   RefineContract

	PlanMax       int
	PerTaskBudget int
	ExecuteMax    int
	ReplanMax     int
	// CompositeRetryMax 表示复合路由失败后的最大重试次数（不含首次尝试）。
	CompositeRetryMax int

	PlanUsed   int
	ReplanUsed int

	MaxRounds     int
	RepairReserve int
	RoundUsed     int
	ActionLogs    []string

	ConsecutiveFailures int
	ThinkingBoostArmed  bool
	ObservationHistory  []ReactRoundObservation

	CurrentPlan      PlannerPlan
	BatchMoveAllowed bool
	// DisableCompositeTools=true 表示已进入 ReAct 兜底，禁止再调用复合工具。
	DisableCompositeTools bool
	// CompositeRouteTried 标记是否尝试过"复合批处理路由"。
	CompositeRouteTried bool
	// CompositeRouteSucceeded 标记复合批处理路由是否已完成"复合分支出站"。
	//
	// 说明：
	// 1. true 表示当前链路可以跳过 ReAct 兜底，直接进入 hard_check；
	// 2. 它不等价于"终审已通过"，终审是否通过仍以后续 HardCheck 结果为准；
	// 3. 这样区分是为了避免"复合工具已成功执行，但业务目标要等终审裁决"时被误判为失败。
	CompositeRouteSucceeded bool
	TaskActionUsed          map[int]int
	EntriesVersion          int
	SeenSlotQueries         map[string]struct{}

	// RequiredCompositeTool 表示本轮策略要求"必须至少成功一次"的复合工具。
	// 取值约定："" | "SpreadEven" | "MinContextSwitch"。
	RequiredCompositeTool string
	// CompositeToolCalled 记录复合工具是否至少调用过一次（不区分成功失败）。
	CompositeToolCalled map[string]bool
	// CompositeToolSuccess 记录复合工具是否至少成功过一次。
	CompositeToolSuccess map[string]bool

	SlicePlan          RefineSlicePlan
	Objective          RefineObjective
	WorksetTaskIDs     []int
	WorksetCursor      int
	CurrentTaskID      int
	CurrentTaskAttempt int

	LastFailedCallSignature string
	OriginOrderMap          map[int]int

	// 4) 终审状态
	HardCheck HardCheckReport

	// 5) 最终输出
	FinalSummary string
	Completed    bool
}

// NewScheduleRefineState 基于上一版预览快照初始化状态。
//
// 职责边界：
// 1. 负责初始化预算、上下文字段与可变状态容器；
// 2. 负责拷贝 preview 数据，避免跨请求引用污染；
// 3. 不负责做任何调度动作。
func NewScheduleRefineState(traceID string, userID int, conversationID string, userMessage string, preview *model.SchedulePlanPreviewCache) *ScheduleRefineState {
	now := nowToMinute()
	st := &ScheduleRefineState{
		TraceID:            strings.TrimSpace(traceID),
		UserID:             userID,
		ConversationID:     strings.TrimSpace(conversationID),
		UserMessage:        strings.TrimSpace(userMessage),
		RequestNow:         now,
		RequestNowText:     now.In(loadLocation()).Format(datetimeLayout),
		PlanMax:            defaultPlanMax,
		PerTaskBudget:      defaultPerTaskBudget,
		ExecuteMax:         defaultExecuteMax,
		ReplanMax:          defaultReplanMax,
		CompositeRetryMax:  defaultCompositeRetry,
		RepairReserve:      defaultRepairReserve,
		MaxRounds:          defaultExecuteMax + defaultRepairReserve,
		ActionLogs:         make([]string, 0, 32),
		ObservationHistory: make([]ReactRoundObservation, 0, 24),
		TaskActionUsed:     make(map[int]int),
		SeenSlotQueries:    make(map[string]struct{}),
		OriginOrderMap:     make(map[int]int),
		CompositeToolCalled: map[string]bool{
			"SpreadEven":       false,
			"MinContextSwitch": false,
		},
		CompositeToolSuccess: map[string]bool{
			"SpreadEven":       false,
			"MinContextSwitch": false,
		},
		CurrentPlan: PlannerPlan{
			Summary: "initialized, waiting for planner output",
		},
		SlicePlan: RefineSlicePlan{
			Reason: "尚未切片",
		},
	}
	if preview == nil {
		return st
	}

	st.TaskClassIDs = append([]int(nil), preview.TaskClassIDs...)
	st.InitialHybridEntries = cloneHybridEntries(preview.HybridEntries)
	st.HybridEntries = cloneHybridEntries(preview.HybridEntries)
	st.AllocatedItems = cloneTaskClassItems(preview.AllocatedItems)
	st.CandidatePlans = cloneWeekSchedules(preview.CandidatePlans)
	st.OriginOrderMap = buildOriginOrderMap(st.HybridEntries)
	return st
}

func loadLocation() *time.Location {
	return agentshared.ShanghaiLocation()
}

func nowToMinute() time.Time {
	return agentshared.NowToMinute()
}

func cloneHybridEntries(src []model.HybridScheduleEntry) []model.HybridScheduleEntry {
	return agentshared.CloneHybridEntries(src)
}

func cloneTaskClassItems(src []model.TaskClassItem) []model.TaskClassItem {
	return agentshared.CloneTaskClassItems(src)
}

func cloneWeekSchedules(src []model.UserWeekSchedule) []model.UserWeekSchedule {
	return agentshared.CloneWeekSchedules(src)
}

// buildOriginOrderMap 构建 suggested 任务的初始顺序基线（task_item_id -> rank）。
func buildOriginOrderMap(entries []model.HybridScheduleEntry) map[int]int {
	orderMap := make(map[int]int)
	if len(entries) == 0 {
		return orderMap
	}
	suggested := make([]model.HybridScheduleEntry, 0, len(entries))
	for _, entry := range entries {
		if isMovableSuggestedTask(entry) {
			suggested = append(suggested, entry)
		}
	}
	sort.SliceStable(suggested, func(i, j int) bool {
		left := suggested[i]
		right := suggested[j]
		if left.Week != right.Week {
			return left.Week < right.Week
		}
		if left.DayOfWeek != right.DayOfWeek {
			return left.DayOfWeek < right.DayOfWeek
		}
		if left.SectionFrom != right.SectionFrom {
			return left.SectionFrom < right.SectionFrom
		}
		if left.SectionTo != right.SectionTo {
			return left.SectionTo < right.SectionTo
		}
		return left.TaskItemID < right.TaskItemID
	})
	for i, entry := range suggested {
		orderMap[entry.TaskItemID] = i + 1
	}
	return orderMap
}

// FinalHardCheckPassed 判断"最终终审"是否整体通过。
//
// 职责边界：
// 1. 负责聚合 physics/order/intent 三类硬校验结果，给服务层与总结阶段统一复用；
// 2. 不负责触发终审，也不负责推送修复动作；
// 3. nil state 视为未通过，避免上层把缺失结果误判为成功。
func FinalHardCheckPassed(st *ScheduleRefineState) bool {
	if st == nil {
		return false
	}
	return st.HardCheck.PhysicsPassed && st.HardCheck.OrderPassed && st.HardCheck.IntentPassed
}

func isMovableSuggestedTask(entry model.HybridScheduleEntry) bool {
	if strings.TrimSpace(entry.Status) != "suggested" || entry.TaskItemID <= 0 {
		return false
	}
	if strings.EqualFold(strings.TrimSpace(entry.Type), "course") {
		return false
	}
	return true
}
