package kb

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

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

var ErrOfficialToDraftConflict = errors.New("official to draft transition changed")

type ConvertKnowledgeBaseOfficialToDraftParams struct {
	WorkspaceID     string
	KnowledgeBaseID string
	WorkingRevision string
	ActorID         string
	AuditEvent      AuditEvent
	Now             time.Time
}

// ConvertKnowledgeBaseOfficialToDraft atomically removes the reader-visible
// Official revision and establishes a fresh, resumable Draft setup session.
// Any incomplete Official editor is deliberately replaced by the new Draft
// revision so hidden working metadata cannot leak into the conversion.
func (s *Store) ConvertKnowledgeBaseOfficialToDraft(ctx context.Context, params ConvertKnowledgeBaseOfficialToDraftParams) (KnowledgeBaseSetupSession, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseSetupSession{}, err
	}
	params.WorkspaceID = strings.TrimSpace(params.WorkspaceID)
	params.KnowledgeBaseID = strings.TrimSpace(params.KnowledgeBaseID)
	params.WorkingRevision = strings.TrimSpace(params.WorkingRevision)
	params.ActorID = strings.TrimSpace(params.ActorID)
	if params.WorkspaceID == "" || params.KnowledgeBaseID == "" || params.WorkingRevision == "" || params.ActorID == "" || params.Now.IsZero() {
		return KnowledgeBaseSetupSession{}, fmt.Errorf("convert Official knowledge base to Draft: scoped identities and time are required")
	}

	var converted KnowledgeBaseSetupSession
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var current KnowledgeBaseSetupSession
		sessionErr := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("workspace_id = ? AND knowledge_base_id = ?", params.WorkspaceID, params.KnowledgeBaseID).
			First(&current).Error
		if sessionErr != nil && !errors.Is(sessionErr, gorm.ErrRecordNotFound) {
			return sessionErr
		}

		var base KnowledgeBase
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("workspace_id = ? AND id = ? AND deleted_at IS NULL", params.WorkspaceID, params.KnowledgeBaseID).
			First(&base).Error; err != nil {
			return err
		}
		if base.PublicationState != "official" || !base.IsActive {
			return ErrOfficialToDraftConflict
		}
		if errors.Is(sessionErr, gorm.ErrRecordNotFound) {
			// Recheck after locking the KB so a rolling-upgrade client cannot
			// insert an Official session between the missing read and conversion.
			sessionErr = tx.Clauses(clause.Locking{Strength: "UPDATE"}).
				Where("workspace_id = ? AND knowledge_base_id = ?", base.WorkspaceID, base.ID).
				First(&current).Error
			if sessionErr != nil && !errors.Is(sessionErr, gorm.ErrRecordNotFound) {
				return sessionErr
			}
		}

		baseUpdate := tx.Model(&KnowledgeBase{}).
			Where("workspace_id = ? AND id = ? AND publication_state = ? AND deleted_at IS NULL", base.WorkspaceID, base.ID, "official").
			Updates(map[string]any{
				"publication_state":  "draft",
				"working_revision":   params.WorkingRevision,
				"published_revision": "",
				"published_at":       nil,
				"updated_by":         params.ActorID,
				"updated_at":         params.Now,
			})
		if baseUpdate.Error != nil {
			return baseUpdate.Error
		}
		if baseUpdate.RowsAffected != 1 {
			return ErrOfficialToDraftConflict
		}

		switch {
		case errors.Is(sessionErr, gorm.ErrRecordNotFound):
			converted = KnowledgeBaseSetupSession{
				ID: params.WorkingRevision + "_setup", WorkspaceID: base.WorkspaceID, KnowledgeBaseID: base.ID,
				Mode: "create_draft", ActiveStep: "basic", WorkingRevision: params.WorkingRevision,
				Version: 1, CreatedBy: params.ActorID, UpdatedBy: params.ActorID,
				CreatedAt: params.Now, UpdatedAt: params.Now,
			}
			if err := tx.Create(&converted).Error; err != nil {
				return err
			}
		case sessionErr != nil:
			return sessionErr
		default:
			result := tx.Model(&KnowledgeBaseSetupSession{}).
				Where("workspace_id = ? AND knowledge_base_id = ? AND version = ?", base.WorkspaceID, base.ID, current.Version).
				Updates(map[string]any{
					"mode": "create_draft", "active_step": "basic",
					"working_revision": params.WorkingRevision, "published_revision": "",
					"working_snapshot_json": "", "published_snapshot_json": "",
					"save_request_id": "", "completed_at": nil,
					"updated_by": params.ActorID, "updated_at": params.Now,
					"version": gorm.Expr("version + 1"),
				})
			if result.Error != nil {
				return result.Error
			}
			if result.RowsAffected != 1 {
				return ErrOfficialToDraftConflict
			}
			if err := tx.Where("workspace_id = ? AND knowledge_base_id = ?", base.WorkspaceID, base.ID).First(&converted).Error; err != nil {
				return err
			}
		}

		if strings.TrimSpace(params.AuditEvent.ID) != "" {
			if _, err := s.WithDB(tx).CreateAuditEvent(ctx, params.AuditEvent); err != nil {
				return err
			}
		}
		return nil
	})
	return converted, err
}
