package content

import (
	"context"
	"fmt"
	"strings"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

const MaxPinListRows = 256

// CreateVersionPin serializes admission on one VersionedContent row, verifies
// the exact immutable version, and enforces that lineage's configured pin cap.
func (s *Store) CreateVersionPin(ctx context.Context, pin VersionPin) (VersionPin, error) {
	if err := s.ensureDB(); err != nil {
		return VersionPin{}, err
	}
	pin.ID = strings.TrimSpace(pin.ID)
	pin.WorkspaceID = strings.TrimSpace(pin.WorkspaceID)
	pin.ContentID = strings.TrimSpace(pin.ContentID)
	pin.VersionID = strings.TrimSpace(pin.VersionID)
	pin.Label = strings.TrimSpace(pin.Label)
	pin.CreatedBy = strings.TrimSpace(pin.CreatedBy)
	if pin.ID == "" || pin.WorkspaceID == "" || pin.ContentID == "" || pin.VersionID == "" || pin.Label == "" || len(pin.Label) > 255 || pin.CreatedBy == "" {
		return VersionPin{}, fmt.Errorf("content version pin identity is invalid")
	}
	if pin.CreatedAt.IsZero() {
		pin.CreatedAt = time.Now().UTC()
	}
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var content VersionedContent
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Select("id", "max_pins").
			Where("workspace_id = ? AND id = ?", pin.WorkspaceID, pin.ContentID).
			First(&content).Error; err != nil {
			return err
		}
		var version ContentVersion
		if err := tx.Select("id").
			Where("workspace_id = ? AND content_id = ? AND id = ?", pin.WorkspaceID, pin.ContentID, pin.VersionID).
			First(&version).Error; err != nil {
			return err
		}
		var duplicate int64
		if err := tx.Model(&VersionPin{}).
			Where("workspace_id = ? AND content_id = ? AND version_id = ? AND label = ?", pin.WorkspaceID, pin.ContentID, pin.VersionID, pin.Label).
			Limit(1).Count(&duplicate).Error; err != nil {
			return err
		}
		if duplicate != 0 {
			return ErrPinConflict
		}
		var count int64
		if err := tx.Model(&VersionPin{}).
			Where("workspace_id = ? AND content_id = ?", pin.WorkspaceID, pin.ContentID).
			Count(&count).Error; err != nil {
			return err
		}
		if count >= int64(content.MaxPins) {
			return ErrQuotaExceeded
		}
		return tx.Create(&pin).Error
	})
	if err != nil {
		return VersionPin{}, fmt.Errorf("create content version pin: %w", err)
	}
	return pin, nil
}

// ListVersionPins returns at most one product-bounded page for an exact
// workspace/content lineage.
func (s *Store) ListVersionPins(ctx context.Context, workspaceID, contentID string, limit int) ([]VersionPin, error) {
	if err := s.ensureDB(); err != nil {
		return nil, err
	}
	workspaceID = strings.TrimSpace(workspaceID)
	contentID = strings.TrimSpace(contentID)
	if workspaceID == "" || contentID == "" || limit <= 0 || limit > MaxPinListRows {
		return nil, fmt.Errorf("content version pin list request is invalid")
	}
	var content VersionedContent
	if err := s.db.WithContext(ctx).Select("id").Where("workspace_id = ? AND id = ?", workspaceID, contentID).First(&content).Error; err != nil {
		return nil, err
	}
	var pins []VersionPin
	err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND content_id = ?", workspaceID, contentID).
		Order("created_at DESC, id DESC").Limit(limit).Find(&pins).Error
	return pins, err
}

// DeleteVersionPin deletes one exact workspace/content pin and reports missing
// or cross-workspace rows as not found.
func (s *Store) DeleteVersionPin(ctx context.Context, workspaceID, contentID, pinID string) error {
	if err := s.ensureDB(); err != nil {
		return err
	}
	result := s.db.WithContext(ctx).
		Where("workspace_id = ? AND content_id = ? AND id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(contentID), strings.TrimSpace(pinID)).
		Delete(&VersionPin{})
	if result.Error != nil {
		return fmt.Errorf("delete content version pin: %w", result.Error)
	}
	if result.RowsAffected != 1 {
		return gorm.ErrRecordNotFound
	}
	return nil
}
