package projects

import (
	"errors"
	"strings"
	"time"

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

var (
	ErrTagNameRequired      = errors.New("tag name is required")
	ErrCronTitleRequired    = errors.New("cron plan title is required")
	ErrCronScheduleRequired = errors.New("cron schedule is required")
)

func (s *Service) CreateCronPlan(ownerID uint, projectID uint, input CreateCronPlanInput) (*models.SenlinAgentCronPlan, error) {
	if err := ensureProjectOwner(ownerID, projectID); err != nil {
		return nil, err
	}
	title := strings.TrimSpace(input.Title)
	if title == "" {
		return nil, ErrCronTitleRequired
	}
	schedule := strings.TrimSpace(input.Schedule)
	if schedule == "" {
		return nil, ErrCronScheduleRequired
	}
	plan := &models.SenlinAgentCronPlan{
		ProjectID: projectID, CreatedBy: ownerID, Title: title, Schedule: schedule,
		Enabled: input.Enabled, NextRunAt: input.NextRunAt,
	}
	return plan, models.DBService.Create(plan).Error
}

func (s *Service) CreateProjectTag(ownerID uint, projectID uint, name string) (*models.SenlinAgentTag, error) {
	if err := ensureProjectOwner(ownerID, projectID); err != nil {
		return nil, err
	}
	return s.CreateTag(projectID, name)
}

func (s *Service) CreateTag(projectID uint, name string) (*models.SenlinAgentTag, error) {
	name = strings.TrimSpace(name)
	if name == "" {
		return nil, ErrTagNameRequired
	}
	var lastErr error
	for attempt := 0; attempt < 5; attempt++ {
		candidate := models.SenlinAgentTag{ProjectID: projectID, Name: name}
		lastErr = models.DBService.Clauses(clause.OnConflict{
			Columns:   []clause.Column{{Name: "project_id"}, {Name: "name"}},
			DoNothing: true,
		}).Create(&candidate).Error
		if lastErr == nil {
			var stored models.SenlinAgentTag
			lastErr = models.DBService.Where("project_id = ? AND name = ?", projectID, name).First(&stored).Error
			if lastErr == nil {
				return &stored, nil
			}
		}
		if !retryableTagWrite(lastErr) {
			return nil, lastErr
		}
		time.Sleep(time.Duration(attempt+1) * time.Millisecond)
	}
	return nil, lastErr
}

func retryableTagWrite(err error) bool {
	if errors.Is(err, gorm.ErrDuplicatedKey) || errors.Is(err, gorm.ErrRecordNotFound) || strings.Contains(strings.ToLower(err.Error()), "database is locked") {
		return true
	}
	var sqlState interface{ SQLState() string }
	if errors.As(err, &sqlState) {
		switch sqlState.SQLState() {
		case "40001", "40P01", "55P03":
			return true
		}
	}
	return false
}

func (s *Service) ListProjectTags(ownerID uint, projectID uint) ([]models.SenlinAgentTag, error) {
	if err := ensureProjectOwner(ownerID, projectID); err != nil {
		return nil, err
	}
	return s.ListTags(projectID)
}

func (s *Service) ListTags(projectID uint) ([]models.SenlinAgentTag, error) {
	var tags []models.SenlinAgentTag
	err := models.DBService.Where("project_id = ?", projectID).Order("name asc").Find(&tags).Error
	return tags, err
}

func ensureProjectOwner(ownerID uint, projectID uint) error {
	var count int64
	if err := models.DBService.Model(&models.SenlinAgentProject{}).Where("id = ? AND owner_id = ?", projectID, ownerID).Count(&count).Error; err != nil {
		return err
	}
	if count == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}
