package newagentconv

import (
	"context"
	"fmt"

	baseconv "github.com/LoveLosita/smartflow/backend/conv"
	"github.com/LoveLosita/smartflow/backend/dao"
	"github.com/LoveLosita/smartflow/backend/model"
	schedule "github.com/LoveLosita/smartflow/backend/newAgent/tools/schedule"
)

// SchedulePersistorAdapter 实现 model.SchedulePersistor 接口。
// 组合 RepoManager，调用 PersistScheduleChanges 持久化变更。
type SchedulePersistorAdapter struct {
	manager *dao.RepoManager
}

// NewSchedulePersistorAdapter 创建持久化适配器。
func NewSchedulePersistorAdapter(manager *dao.RepoManager) *SchedulePersistorAdapter {
	return &SchedulePersistorAdapter{manager: manager}
}

// PersistScheduleChanges 实现 model.SchedulePersistor 接口。
func (a *SchedulePersistorAdapter) PersistScheduleChanges(ctx context.Context, original, modified *schedule.ScheduleState, userID int) error {
	return PersistScheduleChanges(ctx, a.manager, original, modified, userID)
}

// PersistScheduleChanges 将内存中的 ScheduleState 变更持久化到数据库。
//
// 职责边界：
// 1. 调用 DiffScheduleState 计算变更；
// 2. 在事务中逐个应用变更到数据库；
// 3. 全部成功或全部回滚，保证原子性。
func PersistScheduleChanges(
	ctx context.Context,
	manager *dao.RepoManager,
	original *schedule.ScheduleState,
	modified *schedule.ScheduleState,
	userID int,
) error {
	changes := DiffScheduleState(original, modified)
	if len(changes) == 0 {
		return nil
	}

	return manager.Transaction(ctx, func(txM *dao.RepoManager) error {
		for _, change := range changes {
			if err := applyScheduleChange(ctx, txM, change, userID); err != nil {
				return fmt.Errorf("应用变更失败 [%s %s]: %w", change.Type, change.Name, err)
			}
		}
		return nil
	})
}

// applyScheduleChange 应用单个变更到数据库。
func applyScheduleChange(ctx context.Context, manager *dao.RepoManager, change ScheduleChange, userID int) error {
	switch change.Type {
	case ChangePlace:
		return applyPlaceChange(ctx, manager, change, userID)
	case ChangeMove:
		return applyMoveChange(ctx, manager, change, userID)
	case ChangeUnplace:
		return applyUnplaceChange(ctx, manager, change, userID)
	default:
		return fmt.Errorf("未知变更类型: %s", change.Type)
	}
}

// applyPlaceChange 应用放置变更。
func applyPlaceChange(ctx context.Context, manager *dao.RepoManager, change ScheduleChange, userID int) error {
	if len(change.NewCoords) == 0 {
		return fmt.Errorf("place 变更缺少目标位置")
	}
	switch change.Source {
	case "event":
		return applyPlaceEventSource(ctx, manager, change, userID)
	case "task_item":
		return applyPlaceTaskItem(ctx, manager, change, userID)
	default:
		return fmt.Errorf("place 变更不支持的 source: %s", change.Source)
	}
}

// applyPlaceEventSource 处理 source=event 的放置（为已有 Event 创建 Schedule 记录）。
func applyPlaceEventSource(ctx context.Context, manager *dao.RepoManager, change ScheduleChange, userID int) error {
	if change.SourceID == 0 {
		return fmt.Errorf("place event 变更需要有效的 source_id")
	}
	groups := groupCoordsByWeekDay(change.NewCoords)
	for week, dayGroups := range groups {
		for dayOfWeek, coords := range dayGroups {
			startSection, endSection := minMaxSection(coords)
			schedules := make([]model.Schedule, endSection-startSection+1)
			for sec := startSection; sec <= endSection; sec++ {
				schedules[sec-startSection] = model.Schedule{
					UserID:    userID,
					Week:      week,
					DayOfWeek: dayOfWeek,
					Section:   sec,
					EventID:   change.SourceID,
				}
			}
			if _, err := manager.Schedule.AddSchedules(schedules); err != nil {
				return fmt.Errorf("创建 schedule 失败: %w", err)
			}
		}
	}
	return nil
}

// applyPlaceTaskItem 处理 source=task_item 的放置。
//
// 两条路径：
// 1. 嵌入水课（HostEventID != 0）：在宿主 Schedule 记录上设置 embedded_task_id。
// 2. 普通放置（HostEventID == 0）：新建 ScheduleEvent(type=task) + Schedule 记录。
// 两条路径最终都更新 task_items.embedded_time。
func applyPlaceTaskItem(ctx context.Context, manager *dao.RepoManager, change ScheduleChange, userID int) error {
	if change.SourceID == 0 {
		return fmt.Errorf("place task_item 变更需要有效的 source_id")
	}

	// task_item 只占一段连续时段，取第一个 coord 的 week/dayOfWeek
	first := change.NewCoords[0]
	week, dayOfWeek := first.Week, first.DayOfWeek
	startSection, endSection := minMaxSection(change.NewCoords)

	targetTime := &model.TargetTime{
		Week:        week,
		DayOfWeek:   dayOfWeek,
		SectionFrom: startSection,
		SectionTo:   endSection,
	}

	if change.HostEventID != 0 {
		// 嵌入路径：更新宿主 Schedule 记录的 embedded_task_id
		if err := manager.Schedule.EmbedTaskIntoSchedule(
			startSection, endSection, dayOfWeek, week, userID, change.SourceID,
		); err != nil {
			return fmt.Errorf("嵌入水课失败: %w", err)
		}
	} else {
		// 普通路径：新建 ScheduleEvent + Schedule 记录
		startTime, endTime, err := baseconv.RelativeTimeToRealTime(week, dayOfWeek, startSection, endSection)
		if err != nil {
			return fmt.Errorf("时间转换失败: %w", err)
		}
		relID := change.SourceID
		event := model.ScheduleEvent{
			UserID:        userID,
			Name:          change.Name,
			Type:          "task",
			RelID:         &relID,
			CanBeEmbedded: false,
			StartTime:     startTime,
			EndTime:       endTime,
		}
		eventID, err := manager.Schedule.AddScheduleEvent(&event)
		if err != nil {
			return fmt.Errorf("创建 schedule_event 失败: %w", err)
		}
		schedules := make([]model.Schedule, endSection-startSection+1)
		for i, sec := 0, startSection; sec <= endSection; i, sec = i+1, sec+1 {
			schedules[i] = model.Schedule{
				UserID:    userID,
				Week:      week,
				DayOfWeek: dayOfWeek,
				Section:   sec,
				EventID:   eventID,
				Status:    "normal",
			}
		}
		if _, err := manager.Schedule.AddSchedules(schedules); err != nil {
			return fmt.Errorf("创建 schedule 记录失败: %w", err)
		}
	}

	if err := manager.TaskClass.UpdateTaskClassItemEmbeddedTime(ctx, change.SourceID, targetTime); err != nil {
		return fmt.Errorf("更新 task_item embedded_time 失败: %w", err)
	}
	return nil
}

// applyMoveChange 应用移动变更。
func applyMoveChange(ctx context.Context, manager *dao.RepoManager, change ScheduleChange, userID int) error {
	switch change.Source {
	case "event":
		if change.SourceID != 0 {
			if err := manager.Schedule.DeleteScheduleEventAndSchedule(ctx, change.SourceID, userID); err != nil {
				return fmt.Errorf("删除旧位置失败: %w", err)
			}
		}
	case "task_item":
		// 清理旧位置
		if change.OldHostEventID != 0 {
			// 旧位置是嵌入：清空宿主的 embedded_task_id
			if _, err := manager.Schedule.SetScheduleEmbeddedTaskIDToNull(ctx, change.OldHostEventID); err != nil {
				return fmt.Errorf("清除旧嵌入关系失败: %w", err)
			}
		} else {
			// 旧位置是普通 task event：按 task_item_id 删除
			if err := manager.Schedule.DeleteScheduleEventByTaskItemID(ctx, change.SourceID); err != nil {
				return fmt.Errorf("删除旧 task_item 日程失败: %w", err)
			}
		}
	}
	return applyPlaceChange(ctx, manager, change, userID)
}

// applyUnplaceChange 应用移除变更。
func applyUnplaceChange(ctx context.Context, manager *dao.RepoManager, change ScheduleChange, userID int) error {
	switch change.Source {
	case "event":
		if change.SourceID == 0 {
			return fmt.Errorf("unplace event 变更需要有效的 source_id")
		}
		return manager.Schedule.DeleteScheduleEventAndSchedule(ctx, change.SourceID, userID)
	case "task_item":
		if change.SourceID == 0 {
			return fmt.Errorf("unplace task_item 变更需要有效的 source_id")
		}
		if change.HostEventID != 0 {
			// 是嵌入：清空宿主 Schedule 的 embedded_task_id
			if _, err := manager.Schedule.SetScheduleEmbeddedTaskIDToNull(ctx, change.HostEventID); err != nil {
				return fmt.Errorf("清除嵌入关系失败: %w", err)
			}
		} else {
			// 普通 task event：按 task_item_id 删除
			if err := manager.Schedule.DeleteScheduleEventByTaskItemID(ctx, change.SourceID); err != nil {
				return fmt.Errorf("删除 task_item 日程失败: %w", err)
			}
		}
		if err := manager.TaskClass.DeleteTaskClassItemEmbeddedTime(ctx, change.SourceID); err != nil {
			return fmt.Errorf("清除 task_item embedded_time 失败: %w", err)
		}
		return nil
	default:
		return fmt.Errorf("unplace 变更不支持的 source: %s", change.Source)
	}
}

// ==================== 辅助函数 ====================

// intPtr 返回 int 指针，零值返回 nil。
func intPtr(v int) *int {
	if v == 0 {
		return nil
	}
	return &v
}

// groupCoordsByWeekDay 按周天分组坐标。
func groupCoordsByWeekDay(coords []SlotCoord) map[int]map[int][]SlotCoord {
	result := make(map[int]map[int][]SlotCoord)
	for _, coord := range coords {
		if result[coord.Week] == nil {
			result[coord.Week] = make(map[int][]SlotCoord)
		}
		result[coord.Week][coord.DayOfWeek] = append(result[coord.Week][coord.DayOfWeek], coord)
	}
	return result
}

// minMaxSection 返回坐标列表中的最小和最大节次。
func minMaxSection(coords []SlotCoord) (min, max int) {
	if len(coords) == 0 {
		return 0, 0
	}
	min, max = coords[0].Section, coords[0].Section
	for _, c := range coords[1:] {
		if c.Section < min {
			min = c.Section
		}
		if c.Section > max {
			max = c.Section
		}
	}
	return
}
