package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"juhe-factory/api/internal/billing"
	"juhe-factory/api/internal/model"
	queuepkg "juhe-factory/api/internal/queue"

	"github.com/google/uuid"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func (s *Creative) GetRedrawWorkbench(userID, projectID uuid.UUID) (map[string]any, error) {
	project, err := s.GetProject(userID, projectID)
	if err != nil {
		return nil, err
	}
	source := map[string]any{}
	if err := s.DB.Table("creative_projects project").
		Select(`project.source_video_asset_id,source.public_url AS source_video_url,source.original_name AS source_video_original_name,
			project.subtitle_asset_id,subtitle.public_url AS subtitle_url,subtitle.original_name AS subtitle_original_name,
			project.audio_source,project.source_language,project.redraw_status AS status,project.analysis_message`).
		Joins("LEFT JOIN media_assets source ON source.id=project.source_video_asset_id AND source.deleted_at IS NULL").
		Joins("LEFT JOIN media_assets subtitle ON subtitle.id=project.subtitle_asset_id AND subtitle.deleted_at IS NULL").
		Where("project.id=? AND project.user_id=? AND project.project_type='video_redraw' AND project.deleted_at IS NULL", projectID, userID).
		Take(&source).Error; err != nil {
		return nil, err
	}
	assets := make([]map[string]any, 0)
	if err := s.DB.Table("project_assets asset").
		Select("asset.id,asset.project_id,asset.asset_type,asset.name,asset.user_edited").
		Where("asset.project_id=? AND asset.deleted_at IS NULL", projectID).
		Order("asset.asset_type,asset.created_at").Find(&assets).Error; err != nil {
		return nil, err
	}
	storyboards := make([]map[string]any, 0)
	if err := s.DB.Table("episode_storyboards storyboard").
		Select(`storyboard.*,thumbnail.public_url AS thumbnail_url,0 AS history_count`).
		Joins("LEFT JOIN media_assets thumbnail ON thumbnail.id=storyboard.thumbnail_asset_id").
		Where("storyboard.project_id=? AND storyboard.deleted_at IS NULL", projectID).
		Order("storyboard.sequence_no").Find(&storyboards).Error; err != nil {
		return nil, err
	}
	var scriptRow struct {
		RedrawScript string
	}
	if err := s.DB.Table("creative_projects").Select("coalesce(redraw_script,'') AS redraw_script").
		Where("id=? AND user_id=? AND project_type='video_redraw' AND deleted_at IS NULL", projectID, userID).
		Take(&scriptRow).Error; err != nil {
		return nil, err
	}
	return map[string]any{"project": project, "source": source, "assets": assets, "storyboards": storyboards, "script_content": scriptRow.RedrawScript}, nil
}

func (s *Creative) GetRedrawEpisodeWorkbench(userID, projectID, episodeID uuid.UUID) (map[string]any, error) {
	data, err := s.GetWorkbench(userID, projectID, episodeID)
	if err != nil {
		return nil, err
	}
	episode, ok := data["episode"].(map[string]any)
	if !ok {
		return nil, errors.New("剧集数据无效")
	}
	var script string
	if err := s.DB.Table("project_episodes episode").
		Select("coalesce(episode.redraw_script,'')").
		Joins("JOIN creative_projects project ON project.id=episode.project_id AND project.user_id=? AND project.project_type='video_redraw' AND project.deleted_at IS NULL", userID).
		Where("episode.id=? AND episode.project_id=? AND episode.deleted_at IS NULL", episodeID, projectID).
		Scan(&script).Error; err != nil {
		return nil, err
	}
	return map[string]any{
		"project": data["project"], "episode": episode, "source": episode,
		"assets": data["assets"], "storyboards": data["storyboards"], "script_content": script,
	}, nil
}

// SaveRedrawEpisodeScript 校验项目归属和类型后保存用户编辑的剧集反推剧本。
func (s *Creative) SaveRedrawEpisodeScript(userID, projectID, episodeID uuid.UUID, content string) error {
	return s.DB.Transaction(func(tx *gorm.DB) error {
		var episode model.ProjectEpisode
		if err := tx.Table("project_episodes episode").Select("episode.id").
			Joins("JOIN creative_projects project ON project.id=episode.project_id AND project.user_id=? AND project.project_type='video_redraw' AND project.deleted_at IS NULL", userID).
			Where("episode.id=? AND episode.project_id=? AND episode.deleted_at IS NULL", episodeID, projectID).
			Take(&episode).Error; err != nil {
			return err
		}
		return tx.Model(&model.ProjectEpisode{}).Where("id=?", episode.ID).Update("redraw_script", content).Error
	})
}

func (s *Creative) QueueRedrawEpisodeScript(userID, projectID, episodeID uuid.UUID) (*model.GenerationTask, error) {
	if s.Queue == nil {
		return nil, errors.New("反推任务队列不可用")
	}
	config, err := s.analysisModel(projectID)
	if err != nil {
		return nil, err
	}
	task := &model.GenerationTask{
		RequestID: "script_reverse_" + uuid.NewString(), UserID: userID, ChannelID: &config.ChannelID,
		ModelID: &config.ModelID, ProjectID: &projectID, EpisodeID: &episodeID, TaskType: "prompt_reverse", Status: "submitted",
		InputData: json.RawMessage(`{"mode":"script"}`), EstimatedPoints: "0.00", PrepaidPoints: "0.00",
	}
	if err := s.DB.Transaction(func(tx *gorm.DB) error {
		scriptPrompt, err := selectedPromptContent(tx, userID, "剧本反推")
		if err != nil {
			return err
		}
		task.InputData = mustJSON(map[string]string{"mode": "script", "prompt": scriptPrompt})
		var episode model.ProjectEpisode
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Table("project_episodes episode").Select("episode.*").
			Joins("JOIN creative_projects project ON project.id=episode.project_id AND project.user_id=? AND project.project_type='video_redraw' AND project.deleted_at IS NULL", userID).
			Where("episode.id=? AND episode.project_id=? AND episode.deleted_at IS NULL", episodeID, projectID).Take(&episode).Error; err != nil {
			return err
		}
		if episode.Status != "review" {
			return errors.New("请等待全部分镜提示词反推完成")
		}
		var total, incomplete int64
		if err := tx.Model(&model.EpisodeStoryboard{}).Where("episode_id=? AND deleted_at IS NULL", episodeID).Count(&total).Error; err != nil {
			return err
		}
		if total == 0 {
			return errors.New("暂无可用于反推剧本的分镜提示词")
		}
		if err := tx.Model(&model.EpisodeStoryboard{}).
			Where("episode_id=? AND deleted_at IS NULL AND trim(coalesce(prompt_content,''))=''", episodeID).
			Count(&incomplete).Error; err != nil {
			return err
		}
		if incomplete > 0 {
			return errors.New("请等待所有分镜提示词反推完成")
		}
		var prompts []struct {
			SequenceNo    int    `json:"sequence_no"`
			StartMS       int64  `json:"start_ms"`
			EndMS         int64  `json:"end_ms"`
			PromptContent string `json:"prompt_content"`
		}
		if err := tx.Model(&model.EpisodeStoryboard{}).Select("sequence_no,start_ms,end_ms,prompt_content").
			Where("episode_id=? AND deleted_at IS NULL", episodeID).Order("sequence_no").Find(&prompts).Error; err != nil {
			return err
		}
		task.PromptSnapshot = string(mustJSON(prompts))
		var active int64
		if err := tx.Model(&model.GenerationTask{}).Where("episode_id=? AND task_type='prompt_reverse' AND status IN ?", episodeID, activeTaskStatuses).Count(&active).Error; err != nil {
			return err
		}
		if active > 0 {
			return errors.New("当前剧集已有反推任务正在执行")
		}
		if err := billing.CreateTextGenerationTask(tx, task, config.Pricing, "剧本反推"); err != nil {
			return err
		}
		return tx.Model(&episode).Updates(map[string]any{"status": "generating", "analysis_message": "正在反推完整剧本"}).Error
	}); err != nil {
		return nil, err
	}
	if err := queuepkg.EnqueueID(s.Queue, queuepkg.TypeAnalyzeEpisode, task.ID, 0); err != nil {
		if settleErr := s.failQueuedTask(task.ID, "queue_unavailable", "剧本反推任务入队失败"); settleErr != nil {
			return nil, fmt.Errorf("剧本反推任务入队失败且预扣返还失败: %w", settleErr)
		}
		_ = s.DB.Model(&model.ProjectEpisode{}).Where("id=?", episodeID).Updates(map[string]any{"status": "review", "analysis_message": "剧本反推任务入队失败"}).Error
		return nil, errors.New("反推任务队列暂时不可用，请稍后重试")
	}
	return task, nil
}

func mustJSON(value any) []byte {
	result, _ := json.Marshal(value)
	return result
}

// selectedPromptContent 按用户偏好选取指定类型的提示词内容，未命中用户偏好时兜底取最新系统提示词。
// 用户未设置偏好或系统未配置均属于正常业务情况，使用 Limit(1).Find 避免触发 GORM 的 record not found 日志。
func selectedPromptContent(tx *gorm.DB, userID uuid.UUID, promptType string) (string, error) {
	var selected struct{ Content string }
	if err := tx.Table("prompts p").Select("p.content").
		Joins("JOIN user_prompt_preferences pref ON pref.prompt_id=p.id AND pref.user_id=? AND pref.prompt_type=?", userID, promptType).
		Where("p.type=? AND p.deleted_at IS NULL AND (p.scope='system' OR (p.scope='user' AND p.owner_user_id=?))", promptType, userID).
		Limit(1).Find(&selected).Error; err != nil {
		return "", err
	}
	if strings.TrimSpace(selected.Content) == "" {
		if err := tx.Table("prompts").Select("content").
			Where("type=? AND scope='system' AND deleted_at IS NULL", promptType).
			Order("updated_at DESC").Limit(1).Find(&selected).Error; err != nil {
			return "", err
		}
		if strings.TrimSpace(selected.Content) == "" {
			return "", fmt.Errorf("请先在提示词管理中配置%s提示词", promptType)
		}
	}
	return strings.TrimSpace(selected.Content), nil
}

func (s *Creative) UpdateRedrawAnalysisSettings(userID, projectID uuid.UUID, audioSource, sourceLanguage string) error {
	if audioSource != "video_audio" && audioSource != "subtitle_file" {
		return errors.New("台词来源无效")
	}
	sourceLanguage = strings.TrimSpace(sourceLanguage)
	var language any
	if sourceLanguage != "" {
		language = sourceLanguage
	}
	result := s.DB.Table("creative_projects").
		Where("id=? AND user_id=? AND project_type='video_redraw' AND deleted_at IS NULL", projectID, userID).
		Updates(map[string]any{"audio_source": audioSource, "source_language": language})
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}

func (s *Creative) QueueRedrawAnalysis(userID, projectID uuid.UUID) (*model.GenerationTask, error) {
	return s.queueRedrawAnalysis(userID, projectID, nil)
}

func (s *Creative) QueueRedrawStoryboardAnalysis(userID, projectID, storyboardID uuid.UUID) (*model.GenerationTask, error) {
	return s.queueRedrawAnalysis(userID, projectID, &storyboardID)
}

func (s *Creative) QueueRedrawScript(userID, projectID uuid.UUID) (*model.GenerationTask, error) {
	if s.Queue == nil {
		return nil, errors.New("反推任务队列不可用")
	}
	config, err := s.analysisModel(projectID)
	if err != nil {
		return nil, err
	}
	task := &model.GenerationTask{
		RequestID: "script_reverse_" + uuid.NewString(), UserID: userID, ChannelID: &config.ChannelID,
		ModelID: &config.ModelID, ProjectID: &projectID, TaskType: "prompt_reverse", Status: "submitted",
		InputData: json.RawMessage(`{"mode":"script"}`), EstimatedPoints: "0.00", PrepaidPoints: "0.00",
	}
	if err := s.DB.Transaction(func(tx *gorm.DB) error {
		scriptPrompt, err := selectedPromptContent(tx, userID, "剧本反推")
		if err != nil {
			return err
		}
		task.InputData = mustJSON(map[string]string{"mode": "script", "prompt": scriptPrompt})
		var project model.CreativeProject
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id=? AND user_id=? AND project_type='video_redraw' AND deleted_at IS NULL", projectID, userID).
			Take(&project).Error; err != nil {
			return err
		}
		if project.RedrawStatus != "review" {
			return errors.New("请等待全部分镜提示词反推完成")
		}
		var total, incomplete int64
		if err := tx.Model(&model.EpisodeStoryboard{}).Where("project_id=? AND deleted_at IS NULL", projectID).Count(&total).Error; err != nil {
			return err
		}
		if total == 0 {
			return errors.New("暂无可用于反推剧本的分镜提示词")
		}
		if err := tx.Model(&model.EpisodeStoryboard{}).
			Where("project_id=? AND deleted_at IS NULL AND trim(coalesce(prompt_content,''))=''", projectID).
			Count(&incomplete).Error; err != nil {
			return err
		}
		if incomplete > 0 {
			return errors.New("请等待所有分镜提示词反推完成")
		}
		var storyboardPrompts []struct {
			SequenceNo    int    `json:"sequence_no"`
			StartMS       int64  `json:"start_ms"`
			EndMS         int64  `json:"end_ms"`
			PromptContent string `json:"prompt_content"`
		}
		if err := tx.Model(&model.EpisodeStoryboard{}).Select("sequence_no,start_ms,end_ms,prompt_content").
			Where("project_id=? AND deleted_at IS NULL", projectID).Order("sequence_no").Find(&storyboardPrompts).Error; err != nil {
			return err
		}
		promptSnapshot, err := json.Marshal(storyboardPrompts)
		if err != nil {
			return err
		}
		task.PromptSnapshot = string(promptSnapshot)
		var active int64
		if err := tx.Model(&model.GenerationTask{}).Where("project_id=? AND task_type='prompt_reverse' AND status IN ?", projectID, activeTaskStatuses).Count(&active).Error; err != nil {
			return err
		}
		if active > 0 {
			return errors.New("当前剧本已有反推任务正在执行")
		}
		if err := billing.CreateTextGenerationTask(tx, task, config.Pricing, "剧本反推"); err != nil {
			return err
		}
		return tx.Model(&project).Updates(map[string]any{"redraw_status": "generating", "analysis_message": "正在反推完整剧本"}).Error
	}); err != nil {
		return nil, err
	}
	if err := queuepkg.EnqueueID(s.Queue, queuepkg.TypeAnalyzeEpisode, task.ID, 0); err != nil {
		if settleErr := s.failQueuedTask(task.ID, "queue_unavailable", "剧本反推任务入队失败"); settleErr != nil {
			return nil, fmt.Errorf("剧本反推任务入队失败且预扣返还失败: %w", settleErr)
		}
		_ = s.DB.Model(&model.CreativeProject{}).Where("id=?", projectID).Updates(map[string]any{"redraw_status": "review", "analysis_message": "剧本反推任务入队失败"}).Error
		return nil, errors.New("反推任务队列暂时不可用，请稍后重试")
	}
	return task, nil
}

func (s *Creative) queueRedrawAnalysis(userID, projectID uuid.UUID, storyboardID *uuid.UUID) (*model.GenerationTask, error) {
	if s.Queue == nil {
		return nil, errors.New("反推任务队列不可用")
	}
	config, err := s.analysisModel(projectID)
	if err != nil {
		return nil, err
	}
	requestPrefix := "analysis_"
	input := json.RawMessage(`{}`)
	if storyboardID != nil {
		requestPrefix = "storyboard_analysis_"
		input = json.RawMessage(`{"mode":"storyboard"}`)
	}
	task := &model.GenerationTask{
		RequestID: requestPrefix + uuid.NewString(), UserID: userID, ChannelID: &config.ChannelID,
		ModelID: &config.ModelID, ProjectID: &projectID, StoryboardID: storyboardID,
		TaskType: "prompt_reverse", Status: "submitted", InputData: input, EstimatedPoints: "0.00", PrepaidPoints: "0.00",
	}
	if err := s.DB.Transaction(func(tx *gorm.DB) error {
		var project model.CreativeProject
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id=? AND user_id=? AND project_type='video_redraw' AND deleted_at IS NULL", projectID, userID).
			Take(&project).Error; err != nil {
			return err
		}
		if project.SourceVideoAssetID == nil {
			return errors.New("请先上传原视频")
		}
		if project.AudioSource == "subtitle_file" && project.SubtitleAssetID == nil {
			return errors.New("当前选择字幕文件识别，请先上传字幕")
		}
		if storyboardID != nil {
			var storyboard model.EpisodeStoryboard
			if err := tx.Where("id=? AND project_id=? AND deleted_at IS NULL", *storyboardID, projectID).Take(&storyboard).Error; err != nil {
				return err
			}
			if storyboard.Locked {
				return errors.New("当前分镜已保护，无法重新反推")
			}
		}
		var active int64
		if err := tx.Model(&model.GenerationTask{}).
			Where("project_id=? AND task_type='prompt_reverse' AND status IN ?", projectID, activeTaskStatuses).
			Count(&active).Error; err != nil {
			return err
		}
		if active > 0 {
			return errors.New("当前剧本已有反推任务正在执行")
		}
		if err := billing.CreateTextGenerationTask(tx, task, config.Pricing, "视频反推"); err != nil {
			return err
		}
		if storyboardID == nil {
			return tx.Model(&model.CreativeProject{}).Where("id=?", projectID).
				Updates(map[string]any{"redraw_status": "analyzing", "analysis_message": "等待视频分析"}).Error
		}
		return nil
	}); err != nil {
		return nil, err
	}
	if err := queuepkg.EnqueueID(s.Queue, queuepkg.TypeAnalyzeEpisode, task.ID, 0); err != nil {
		if settleErr := s.failQueuedTask(task.ID, "queue_unavailable", "反推任务入队失败"); settleErr != nil {
			return nil, fmt.Errorf("反推任务入队失败且预扣返还失败: %w", settleErr)
		}
		return nil, errors.New("反推任务队列暂时不可用，请稍后重试")
	}
	return task, nil
}

func (s *Creative) ResetRedrawAnalysis(userID, projectID uuid.UUID) ([]string, error) {
	media := newDeletionMediaSet()
	err := s.DB.Transaction(func(tx *gorm.DB) error {
		var project model.CreativeProject
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id=? AND user_id=? AND project_type='video_redraw' AND deleted_at IS NULL", projectID, userID).
			Take(&project).Error; err != nil {
			return err
		}
		var active int64
		if err := tx.Model(&model.GenerationTask{}).Where("project_id=? AND status IN ?", projectID, activeTaskStatuses).Count(&active).Error; err != nil {
			return err
		}
		if active > 0 {
			return errors.New("当前剧本存在进行中的任务，暂时不能重新反推")
		}
		if err := removeRedrawDerivedContent(tx, projectID, media); err != nil {
			return err
		}
		if err := media.deleteRows(tx); err != nil {
			return err
		}
		return tx.Model(&project).Updates(map[string]any{"redraw_status": "uploaded", "analysis_message": nil, "redraw_script": nil}).Error
	})
	return media.objectKeys(), err
}

func removeRedrawDerivedContent(tx *gorm.DB, projectID uuid.UUID, media *deletionMediaSet) error {
	taskScope := `project_id=? AND (task_type IN ('prompt_reverse','video_generation')
		OR (task_type='image_generation' AND input_data->>'target_type'='storyboard'))`
	if err := media.addQuery(tx.Table("media_assets media").Select("DISTINCT media.id,media.object_key").
		Joins("JOIN generation_outputs output ON output.media_asset_id=media.id").
		Joins("JOIN generation_tasks task ON task.id=output.task_id").Where(taskScope, projectID)); err != nil {
		return err
	}
	if err := media.addQuery(tx.Table("media_assets media").Select("DISTINCT media.id,media.object_key").
		Joins("JOIN episode_storyboards storyboard ON storyboard.thumbnail_asset_id=media.id").
		Where("storyboard.project_id=?", projectID)); err != nil {
		return err
	}
	if err := collectMediaByObjectKey(tx, media, fmt.Sprintf("juyou_ran/video-redraw/projects/%s/storyboards/%%", projectID)); err != nil {
		return err
	}
	if err := collectMediaByObjectKey(tx, media, fmt.Sprintf("juyou_ran/video-redraw/projects/%s/episodes/%%/storyboards/%%", projectID)); err != nil {
		return err
	}
	if err := tx.Model(&model.CreativeProject{}).Where("id=?", projectID).Update("cover_asset_id", nil).Error; err != nil {
		return err
	}
	if err := tx.Exec("UPDATE episode_storyboards SET active_output_id=NULL WHERE project_id=?", projectID).Error; err != nil {
		return err
	}
	if err := tx.Exec("DELETE FROM generation_outputs WHERE task_id IN (SELECT id FROM generation_tasks WHERE "+taskScope+")", projectID).Error; err != nil {
		return err
	}
	if err := tx.Exec("DELETE FROM generation_tasks WHERE "+taskScope, projectID).Error; err != nil {
		return err
	}
	return tx.Exec("DELETE FROM episode_storyboards WHERE project_id=?", projectID).Error
}
