package tasks

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"senlinai-agent/backend/internal/models"
)

type Service struct {
	db *gorm.DB
}

type LinkedObject struct {
	ObjectType string `json:"objectType"`
	ObjectID   string `json:"objectId"`
}

// NewService 接受数据库或上层事务，确保任务写入、标签调整和分享检查使用同一依赖。
func NewService(databases ...*gorm.DB) *Service {
	var database *gorm.DB
	if len(databases) > 0 {
		database = databases[0]
	}
	return &Service{db: database}
}

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

// Assign 在同一事务内由用户 identity 解析内部主键，并同步两个指派字段，避免 DTO 返回陈旧 identity。
func (s *Service) Assign(taskID uint, assigneeIdentity string) error {
	return s.database().Transaction(func(tx *gorm.DB) error {
		var task models.SenlinAgentTask
		if err := tx.First(&task, taskID).Error; err != nil {
			return err
		}
		var assignee models.SenlinAgentUser
		if err := tx.Where("identity = ?", assigneeIdentity).First(&assignee).Error; err != nil {
			return err
		}
		if err := tx.Model(&task).Updates(map[string]any{
			"assignee_id": assignee.ID, "assignee_identity": assignee.Identity,
		}).Error; err != nil {
			return err
		}
		return tx.Create(&models.SenlinAgentProjectEvent{
			ProjectID:  task.ProjectID,
			ActorID:    task.CreatedBy,
			EventType:  "task_assigned",
			EntityType: "task",
			EntityID:   task.ID,
			Summary:    fmt.Sprintf("Task assigned to user %s", assignee.Identity),
		}).Error
	})
}

// ShareObject 只允许显式分享 note/source，并在同一事务中验证关联对象属于任务所在项目。
func (s *Service) ShareObject(taskID uint, objectType string, objectID uint) error {
	if objectType != "note" && objectType != "source" {
		return errors.New("unsupported shared object type")
	}
	return s.database().Transaction(func(tx *gorm.DB) error {
		task, err := lockTaskByID(tx, taskID)
		if err != nil {
			return err
		}
		if err := ensureSharedObjectInProject(tx, task.ProjectID, objectType, objectID); err != nil {
			return err
		}
		if err := tx.Create(&models.SenlinAgentTaskShare{TaskID: taskID, ObjectType: objectType, ObjectID: objectID}).Error; err != nil {
			return err
		}
		return tx.Create(&models.SenlinAgentProjectEvent{
			ProjectID:  task.ProjectID,
			ActorID:    task.CreatedBy,
			EventType:  "task_object_shared",
			EntityType: "task",
			EntityID:   task.ID,
			Summary:    fmt.Sprintf("Shared %s %d with task", objectType, objectID),
		}).Error
	})
}

// VisibleLinkedObjects 对被指派人也只返回显式分享记录，不因任务可见而扩大关联对象权限。
func (s *Service) VisibleLinkedObjects(taskID uint, viewerID uint) ([]LinkedObject, error) {
	var task models.SenlinAgentTask
	if err := s.database().First(&task, taskID).Error; err != nil {
		return nil, err
	}
	if task.AssigneeID == nil || *task.AssigneeID != viewerID {
		return []LinkedObject{}, nil
	}
	var shares []models.SenlinAgentTaskShare
	if err := s.database().Where("task_id = ?", taskID).Find(&shares).Error; err != nil {
		return nil, err
	}
	objects := make([]LinkedObject, 0, len(shares))
	for _, share := range shares {
		objects = append(objects, LinkedObject{ObjectType: share.ObjectType, ObjectID: share.ObjectIdentity})
	}
	return objects, nil
}

var ErrTaskTitleRequired = errors.New("task title is required")

// Create 在同一事务中校验项目所有权、解析项目内标签并创建任务。
func (s *Service) Create(ownerID uint, projectIdentity string, input CreateTaskInput) (TaskDTO, error) {
	var result TaskDTO
	err := s.database().Transaction(func(tx *gorm.DB) error {
		project, err := findOwnedProject(tx, ownerID, projectIdentity)
		if err != nil {
			return err
		}
		title := strings.TrimSpace(input.Title)
		if title == "" {
			return ErrTaskTitleRequired
		}
		status := strings.TrimSpace(input.Status)
		if status == "" {
			status = "open"
		}
		tagID, tagIdentity, tagName, err := findOrCreateProjectTag(tx, project.ID, input.Tag)
		if err != nil {
			return err
		}
		task := models.SenlinAgentTask{
			ProjectID: project.ID, CreatedBy: ownerID, TagID: tagID,
			Title: title, Description: strings.TrimSpace(input.Description), Status: status, DueAt: utcOptionalTime(input.DueAt),
		}
		if err := tx.Create(&task).Error; err != nil {
			return err
		}
		result = makeTaskDTO(task, project.Identity, tagIdentity, tagName)
		return nil
	})
	return result, err
}

// Update 同时按项目和任务 identity 查询；移动项目时重新校验所有权，并重新解析目标项目标签。
func (s *Service) Update(ownerID uint, projectIdentity, taskIdentity string, input UpdateTaskInput) (TaskDTO, error) {
	var result TaskDTO
	err := s.database().Transaction(func(tx *gorm.DB) error {
		task, err := lockTaskByIdentity(tx, taskIdentity)
		if err != nil {
			return err
		}
		currentProject, err := findOwnedProject(tx, ownerID, projectIdentity)
		if err != nil {
			return err
		}
		if task.ProjectID != currentProject.ID {
			return gorm.ErrRecordNotFound
		}
		targetProject := currentProject
		if strings.TrimSpace(input.NextProjectIdentity) != "" && input.NextProjectIdentity != currentProject.Identity {
			targetProject, err = findOwnedProject(tx, ownerID, input.NextProjectIdentity)
			if err != nil {
				return err
			}
		}
		title := strings.TrimSpace(input.Title)
		if title == "" {
			return ErrTaskTitleRequired
		}
		status := strings.TrimSpace(input.Status)
		if status == "" {
			if input.Completed {
				status = "done"
			} else {
				status = "open"
			}
		}

		// 标签只能在目标项目内重新解析；未提供标签时会清空旧项目标签，不能跨项目沿用。
		tagID, tagIdentity, tagName, err := findOrCreateProjectTag(tx, targetProject.ID, input.Tag)
		if err != nil {
			return err
		}
		task.ProjectID = targetProject.ID
		task.ProjectIdentity = targetProject.Identity
		task.Title = title
		task.Description = strings.TrimSpace(input.Description)
		task.Status = status
		task.TagID = tagID
		task.TagIdentity = tagIdentity
		if targetProject.ID != currentProject.ID {
			// 现有分享都在原项目边界内；移动后必须清空，避免旧项目 note/source 继续对被指派人可见。
			if err := tx.Where("task_id = ?", task.ID).Delete(&models.SenlinAgentTaskShare{}).Error; err != nil {
				return err
			}
		}
		if err := tx.Save(task).Error; err != nil {
			return err
		}
		result = makeTaskDTO(*task, targetProject.Identity, tagIdentity, tagName)
		return nil
	})
	return result, err
}

func lockTaskByID(tx *gorm.DB, taskID uint) (*models.SenlinAgentTask, error) {
	var task models.SenlinAgentTask
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&task, taskID).Error; err != nil {
		return nil, err
	}
	return &task, nil
}

func lockTaskByIdentity(tx *gorm.DB, taskIdentity string) (*models.SenlinAgentTask, error) {
	var task models.SenlinAgentTask
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("identity = ?", taskIdentity).First(&task).Error; err != nil {
		return nil, err
	}
	return &task, nil
}

func findOwnedProject(tx *gorm.DB, ownerID uint, identity string) (*models.SenlinAgentProject, error) {
	var project models.SenlinAgentProject
	if err := tx.Where("owner_id = ? AND identity = ?", ownerID, identity).First(&project).Error; err != nil {
		return nil, err
	}
	return &project, nil
}

func findOrCreateProjectTag(tx *gorm.DB, projectID uint, value string) (*uint, *string, string, error) {
	name := strings.TrimSpace(value)
	if name == "" {
		return nil, nil, "", nil
	}
	candidate := models.SenlinAgentTag{ProjectID: projectID, Name: name}
	if err := tx.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "project_id"}, {Name: "name"}},
		DoNothing: true,
	}).Create(&candidate).Error; err != nil {
		return nil, nil, "", err
	}
	var tag models.SenlinAgentTag
	if err := tx.Where("project_id = ? AND name = ?", projectID, name).First(&tag).Error; err != nil {
		return nil, nil, "", err
	}
	return &tag.ID, &tag.Identity, tag.Name, nil
}

func makeTaskDTO(task models.SenlinAgentTask, projectIdentity string, tagIdentity *string, tagName string) TaskDTO {
	return TaskDTO{
		ID: task.Identity, ProjectID: projectIdentity, Title: task.Title, Description: task.Description,
		Status: task.Status, Completed: task.Status == "done", DueAt: utcOptionalTime(task.DueAt),
		AssigneeID: task.AssigneeIdentity, TagID: tagIdentity, Tag: tagName,
		SourceInboxItemID: task.SourceInboxItemIdentity, CreatedAt: task.CreatedAt.UTC(), UpdatedAt: task.UpdatedAt.UTC(),
	}
}

func utcOptionalTime(value *time.Time) *time.Time {
	if value == nil {
		return nil
	}
	result := value.UTC()
	return &result
}

func ensureSharedObjectInProject(tx *gorm.DB, projectID uint, objectType string, objectID uint) error {
	switch objectType {
	case "note":
		var count int64
		if err := tx.Model(&models.SenlinAgentNote{}).Where("id = ? AND project_id = ?", objectID, projectID).Count(&count).Error; err != nil {
			return err
		}
		if count == 0 {
			return errors.New("shared object not found in task project")
		}
	case "source":
		var count int64
		if err := tx.Model(&models.SenlinAgentSource{}).Where("id = ? AND project_id = ?", objectID, projectID).Count(&count).Error; err != nil {
			return err
		}
		if count == 0 {
			return errors.New("shared object not found in task project")
		}
	}
	return nil
}
