package inbox

import (
	"errors"
	"strings"

	"github.com/google/uuid"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"senlinai-agent/backend/internal/models"
)

var (
	ErrProjectAndUserRequired      = errors.New("project and user are required")
	ErrSourceTypeRequired          = errors.New("source type is required")
	ErrInvalidSuggestion           = errors.New("invalid inbox suggestion")
	ErrSuggestionSelectionRequired = errors.New("suggestion selection is required")
	ErrSuggestionNotFound          = errors.New("selected suggestion was not saved for inbox item")
	ErrInboxAlreadyConfirmed       = errors.New("inbox item already confirmed")
)

type CaptureInput struct {
	ProjectIdentity string
	UserID          uint
	SourceType      string
	Title           string
	Body            string
}

// Suggestion 是分析接口返回的候选草稿；ID 由服务端保存后生成，确认接口不再接受 kind/title/body。
type Suggestion struct {
	ID    string `json:"id"`
	Kind  string `json:"kind"`
	Title string `json:"title"`
	Body  string `json:"body"`
}

type ConfirmResult struct {
	CreatedCount int `json:"createdCount"`
}

type Analyzer interface {
	Analyze(item models.SenlinAgentInboxItem, userID uint) ([]Suggestion, error)
}

type StaticAnalyzer struct {
	Suggestions []Suggestion
}

func (a StaticAnalyzer) Analyze(item models.SenlinAgentInboxItem, userID uint) ([]Suggestion, error) {
	if len(a.Suggestions) > 0 {
		return a.Suggestions, nil
	}
	subject := suggestionSubject(item)
	body := strings.TrimSpace(item.Body)
	if body == "" {
		body = strings.TrimSpace(item.Title)
	}
	// 默认分析器只把用户主动收集的内容整理成可审阅草稿；它不调用 provider，也不创建正式对象。
	return []Suggestion{
		{Kind: "task", Title: "跟进：" + subject, Body: body},
		{Kind: "note", Title: subject + "整理笔记", Body: body},
		{Kind: "source", Title: subject + "背景资料", Body: body},
	}, nil
}

type Service struct {
	analyzer Analyzer
	db       *gorm.DB
}

func NewService(analyzer Analyzer, databases ...*gorm.DB) *Service {
	var database *gorm.DB
	if len(databases) > 0 {
		database = databases[0]
	}
	return &Service{analyzer: analyzer, db: database}
}

func (s *Service) database() *gorm.DB {
	if s.db != nil {
		return s.db
	}
	return models.DBService
}

// Capture 先以 owner + project identity 校验项目边界，再创建归属于当前用户的原始 Inbox 条目。
func (s *Service) Capture(input CaptureInput) (*models.SenlinAgentInboxItem, error) {
	if strings.TrimSpace(input.ProjectIdentity) == "" || input.UserID == 0 {
		return nil, ErrProjectAndUserRequired
	}
	sourceType := strings.TrimSpace(input.SourceType)
	if sourceType == "" {
		return nil, ErrSourceTypeRequired
	}
	var item models.SenlinAgentInboxItem
	err := s.database().Transaction(func(tx *gorm.DB) error {
		var project models.SenlinAgentProject
		if err := tx.Where("owner_id = ? AND identity = ?", input.UserID, input.ProjectIdentity).First(&project).Error; err != nil {
			return err
		}
		item = models.SenlinAgentInboxItem{
			ProjectID: project.ID, ProjectIdentity: project.Identity, CreatedBy: input.UserID,
			SourceType: sourceType, Title: strings.TrimSpace(input.Title), Body: strings.TrimSpace(input.Body), Status: "open",
		}
		return tx.Create(&item).Error
	})
	return &item, err
}

// Analyze 只保存候选草稿，不创建任何任务、笔记或资料；重新分析会原子替换该条目的旧草稿。
func (s *Service) Analyze(itemIdentity string, userID uint) ([]Suggestion, error) {
	item, err := findOwnedInboxItem(s.database(), itemIdentity, userID, false)
	if err != nil {
		return nil, err
	}
	if item.Status != "open" {
		return nil, ErrInboxAlreadyConfirmed
	}
	generated := []Suggestion{}
	if s.analyzer != nil {
		generated, err = s.analyzer.Analyze(*item, userID)
		if err != nil {
			return nil, err
		}
	}
	normalized, err := normalizeSuggestions(generated)
	if err != nil {
		return nil, err
	}

	saved := make([]Suggestion, 0, len(normalized))
	err = s.database().Transaction(func(tx *gorm.DB) error {
		locked, err := findOwnedInboxItem(tx, itemIdentity, userID, true)
		if err != nil {
			return err
		}
		if locked.Status != "open" {
			return ErrInboxAlreadyConfirmed
		}
		if err := tx.Where("inbox_item_id = ?", locked.ID).Delete(&models.SenlinAgentInboxSuggestion{}).Error; err != nil {
			return err
		}
		for _, suggestion := range normalized {
			draft := models.SenlinAgentInboxSuggestion{
				InboxItemID: locked.ID, InboxItemIdentity: locked.Identity, CreatedBy: userID,
				Kind: suggestion.Kind, Title: suggestion.Title, Body: suggestion.Body,
			}
			if err := tx.Create(&draft).Error; err != nil {
				return err
			}
			saved = append(saved, Suggestion{ID: draft.Identity, Kind: draft.Kind, Title: draft.Title, Body: draft.Body})
		}
		return nil
	})
	return saved, err
}

// Confirm 只按服务端保存的建议 identity 创建对象；全部写入和 Inbox 状态变更位于同一事务中。
func (s *Service) Confirm(itemIdentity string, userID uint, selectedSuggestionIdentities []string) (ConfirmResult, error) {
	result := ConfirmResult{}
	err := s.database().Transaction(func(tx *gorm.DB) error {
		item, err := findOwnedInboxItem(tx, itemIdentity, userID, true)
		if err != nil {
			return err
		}
		if item.Status != "open" {
			result.CreatedCount = item.ConfirmedCount
			return nil
		}

		selected, err := normalizeSelectedIdentities(selectedSuggestionIdentities)
		if err != nil {
			return err
		}

		var saved []models.SenlinAgentInboxSuggestion
		if err := tx.Where("inbox_item_id = ? AND created_by = ? AND identity IN ?", item.ID, userID, selected).Find(&saved).Error; err != nil {
			return err
		}
		if len(saved) != len(selected) {
			return ErrSuggestionNotFound
		}
		byIdentity := make(map[string]models.SenlinAgentInboxSuggestion, len(saved))
		for _, suggestion := range saved {
			byIdentity[suggestion.Identity] = suggestion
		}
		for _, identity := range selected {
			suggestion, ok := byIdentity[identity]
			if !ok {
				return ErrSuggestionNotFound
			}
			if err := createConfirmedObject(tx, *item, suggestion); err != nil {
				return err
			}
			result.CreatedCount++
		}
		return tx.Model(item).Updates(map[string]any{
			"status":          "processed",
			"confirmed_count": result.CreatedCount,
		}).Error
	})
	return result, err
}

func findOwnedInboxItem(tx *gorm.DB, identity string, userID uint, lock bool) (*models.SenlinAgentInboxItem, error) {
	var item models.SenlinAgentInboxItem
	query := tx.Model(&models.SenlinAgentInboxItem{}).
		Joins("JOIN senlin_agent_projects ON senlin_agent_projects.id = senlin_agent_inbox_items.project_id").
		Where("senlin_agent_inbox_items.identity = ? AND senlin_agent_inbox_items.created_by = ? AND senlin_agent_projects.owner_id = ?", identity, userID, userID)
	if lock {
		query = query.Clauses(clause.Locking{Strength: "UPDATE"})
	}
	if err := query.First(&item).Error; err != nil {
		return nil, err
	}
	return &item, nil
}

func normalizeSuggestions(suggestions []Suggestion) ([]Suggestion, error) {
	result := make([]Suggestion, 0, len(suggestions))
	for _, suggestion := range suggestions {
		kind := strings.ToLower(strings.TrimSpace(suggestion.Kind))
		title := strings.TrimSpace(suggestion.Title)
		if title == "" || (kind != "task" && kind != "note" && kind != "source") {
			return nil, ErrInvalidSuggestion
		}
		result = append(result, Suggestion{Kind: kind, Title: title, Body: strings.TrimSpace(suggestion.Body)})
	}
	return result, nil
}

func suggestionSubject(item models.SenlinAgentInboxItem) string {
	value := strings.TrimSpace(item.Title)
	if value == "" {
		value = strings.TrimSpace(item.Body)
	}
	if value == "" {
		return "Inbox 内容"
	}
	runes := []rune(value)
	if len(runes) > 40 {
		return string(runes[:40]) + "…"
	}
	return value
}

func normalizeSelectedIdentities(values []string) ([]string, error) {
	if len(values) == 0 {
		return nil, ErrSuggestionSelectionRequired
	}
	seen := make(map[string]struct{}, len(values))
	result := make([]string, 0, len(values))
	for _, value := range values {
		identity, err := uuid.Parse(strings.TrimSpace(value))
		if err != nil || identity.Version() != 7 || identity.Variant() != uuid.RFC4122 {
			return nil, ErrSuggestionNotFound
		}
		normalized := identity.String()
		if _, exists := seen[normalized]; exists {
			continue
		}
		seen[normalized] = struct{}{}
		result = append(result, normalized)
	}
	return result, nil
}

func createConfirmedObject(tx *gorm.DB, item models.SenlinAgentInboxItem, suggestion models.SenlinAgentInboxSuggestion) error {
	sourceInboxItemID := item.ID
	switch suggestion.Kind {
	case "task":
		return tx.Create(&models.SenlinAgentTask{
			ProjectID: item.ProjectID, ProjectIdentity: item.ProjectIdentity, CreatedBy: item.CreatedBy,
			SourceInboxItemID: &sourceInboxItemID, Title: suggestion.Title, Description: suggestion.Body, Status: "open",
		}).Error
	case "note":
		return tx.Create(&models.SenlinAgentNote{
			ProjectID: item.ProjectID, ProjectIdentity: item.ProjectIdentity, CreatedBy: item.CreatedBy,
			SourceInboxItemID: &sourceInboxItemID, Title: suggestion.Title, Markdown: suggestion.Body,
		}).Error
	case "source":
		// 文本型 Inbox 资料不是上传文件，不构造或伪造任何本地路径。
		return tx.Create(&models.SenlinAgentSource{
			ProjectID: item.ProjectID, ProjectIdentity: item.ProjectIdentity, CreatedBy: item.CreatedBy,
			SourceInboxItemID: &sourceInboxItemID, Kind: "text", Title: suggestion.Title, ContentText: suggestion.Body,
		}).Error
	default:
		return ErrInvalidSuggestion
	}
}
