package kb

import (
	"github.com/tidbcloud/AgentTeam/internal/kbgovernance"

	"context"
	"errors"
	"fmt"
	"sort"
	"strings"
	"time"

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

const SetupModeEditOfficial = "edit_official"

var (
	ErrOfficialEditConflict       = errors.New("official edit changed")
	ErrOfficialEditReplayConflict = errors.New("official edit save replay conflict")
)

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

// StartKnowledgeBaseOfficialEdit establishes one shared, resumable working
// revision without changing the reader-visible KnowledgeBase metadata.
func (s *Store) StartKnowledgeBaseOfficialEdit(ctx context.Context, params StartKnowledgeBaseOfficialEditParams) (KnowledgeBaseSetupSession, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseSetupSession{}, err
	}
	params.WorkspaceID, params.KnowledgeBaseID = strings.TrimSpace(params.WorkspaceID), strings.TrimSpace(params.KnowledgeBaseID)
	params.WorkingRevision, params.ActorID = strings.TrimSpace(params.WorkingRevision), strings.TrimSpace(params.ActorID)
	if params.WorkspaceID == "" || params.KnowledgeBaseID == "" || params.WorkingRevision == "" || params.ActorID == "" || params.Now.IsZero() {
		return KnowledgeBaseSetupSession{}, fmt.Errorf("start official edit: scoped identities and time are required")
	}
	var started KnowledgeBaseSetupSession
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		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" || strings.TrimSpace(base.PublishedRevision) == "" || !base.IsActive {
			return ErrOfficialEditConflict
		}
		var current KnowledgeBaseSetupSession
		sessionErr := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("workspace_id = ? AND knowledge_base_id = ?", base.WorkspaceID, base.ID).
			First(&current).Error
		if sessionErr == nil && current.Mode == SetupModeEditOfficial && current.CompletedAt == nil &&
			current.PublishedRevision == base.PublishedRevision && current.WorkingRevision == base.WorkingRevision && current.WorkingRevision != "" {
			started = current
			return nil
		}
		if sessionErr != nil && !errors.Is(sessionErr, gorm.ErrRecordNotFound) {
			return sessionErr
		}
		if strings.TrimSpace(base.WorkingRevision) != "" {
			return ErrOfficialEditConflict
		}
		snapshot, err := s.WithDB(tx).loadKnowledgeBasePublicationSnapshot(ctx, base, params.WorkingRevision, true)
		if err != nil {
			return err
		}
		publishedSnapshot := snapshot.Snapshot
		publishedSnapshot.WorkingRevision = base.PublishedRevision
		publishedJSON, _, err := kbgovernance.EncodePublicationSnapshot(publishedSnapshot)
		if err != nil {
			return err
		}
		if errors.Is(sessionErr, gorm.ErrRecordNotFound) {
			started = KnowledgeBaseSetupSession{
				ID: params.WorkingRevision + "_setup", WorkspaceID: base.WorkspaceID, KnowledgeBaseID: base.ID,
				Mode: SetupModeEditOfficial, ActiveStep: "basic", WorkingRevision: params.WorkingRevision,
				PublishedRevision: base.PublishedRevision, WorkingSnapshotJSON: string(snapshot.JSON),
				PublishedSnapshotJSON: string(publishedJSON), Version: 1,
				CreatedBy: params.ActorID, UpdatedBy: params.ActorID, CreatedAt: params.Now, UpdatedAt: params.Now,
			}
			if err := tx.Create(&started).Error; err != nil {
				return err
			}
		} else {
			result := tx.Model(&KnowledgeBaseSetupSession{}).
				Where("workspace_id = ? AND knowledge_base_id = ? AND version = ?", base.WorkspaceID, base.ID, current.Version).
				Updates(map[string]any{
					"mode": SetupModeEditOfficial, "active_step": "basic",
					"working_revision": params.WorkingRevision, "published_revision": base.PublishedRevision,
					"working_snapshot_json": string(snapshot.JSON), "published_snapshot_json": string(publishedJSON),
					"save_request_id": "", "completed_at": nil, "updated_by": params.ActorID,
					"updated_at": params.Now, "version": gorm.Expr("version + 1"),
				})
			if result.Error != nil || result.RowsAffected != 1 {
				if result.Error != nil {
					return result.Error
				}
				return ErrOfficialEditConflict
			}
			if err := tx.Where("workspace_id = ? AND knowledge_base_id = ?", base.WorkspaceID, base.ID).First(&started).Error; err != nil {
				return err
			}
		}
		result := tx.Model(&KnowledgeBase{}).
			Where("workspace_id = ? AND id = ? AND publication_state = 'official' AND published_revision = ? AND working_revision = ''", base.WorkspaceID, base.ID, base.PublishedRevision).
			UpdateColumn("working_revision", params.WorkingRevision)
		if result.Error != nil || result.RowsAffected != 1 {
			if result.Error != nil {
				return result.Error
			}
			return ErrOfficialEditConflict
		}
		if strings.TrimSpace(params.AuditEvent.ID) != "" {
			if _, err := s.WithDB(tx).CreateAuditEvent(ctx, params.AuditEvent); err != nil {
				return err
			}
		}
		return nil
	})
	return started, err
}

type UpdateKnowledgeBaseOfficialEditBasicParams struct {
	WorkspaceID     string
	KnowledgeBaseID string
	ExpectedVersion int64
	Name            string
	Description     string
	GroupID         string
	TagIDs          []string
	ActorID         string
	AuditEvent      AuditEvent
	Now             time.Time
}

func (s *Store) UpdateKnowledgeBaseOfficialEditBasic(ctx context.Context, params UpdateKnowledgeBaseOfficialEditBasicParams) (KnowledgeBaseSetupSession, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseSetupSession{}, err
	}
	params.WorkspaceID, params.KnowledgeBaseID = strings.TrimSpace(params.WorkspaceID), strings.TrimSpace(params.KnowledgeBaseID)
	params.Name, params.Description = strings.TrimSpace(params.Name), strings.TrimSpace(params.Description)
	params.GroupID, params.ActorID = strings.TrimSpace(params.GroupID), strings.TrimSpace(params.ActorID)
	params.TagIDs = uniqueTrimmedStrings(params.TagIDs)
	sort.Strings(params.TagIDs)
	if params.WorkspaceID == "" || params.KnowledgeBaseID == "" || params.ExpectedVersion <= 0 || params.Name == "" || params.GroupID == "" || params.ActorID == "" || params.Now.IsZero() {
		return KnowledgeBaseSetupSession{}, fmt.Errorf("update official edit basic info: scoped revision, fields, actor, and time are required")
	}
	var updated KnowledgeBaseSetupSession
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var session KnowledgeBaseSetupSession
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("workspace_id = ? AND knowledge_base_id = ?", params.WorkspaceID, params.KnowledgeBaseID).First(&session).Error; err != nil {
			return err
		}
		if session.Mode != SetupModeEditOfficial || session.CompletedAt != nil || session.Version != params.ExpectedVersion {
			return ErrOfficialEditConflict
		}
		var base KnowledgeBase
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("workspace_id = ? AND id = ?", params.WorkspaceID, params.KnowledgeBaseID).First(&base).Error; err != nil {
			return err
		}
		if base.PublicationState != "official" || base.PublishedRevision != session.PublishedRevision || base.WorkingRevision != session.WorkingRevision {
			return ErrOfficialEditConflict
		}
		snapshot, err := kbgovernance.DecodePublicationSnapshot([]byte(session.WorkingSnapshotJSON))
		if err != nil || snapshot.KnowledgeBaseID != base.ID || snapshot.WorkingRevision != session.WorkingRevision {
			return ErrOfficialEditConflict
		}
		snapshot.Name, snapshot.Description, snapshot.GroupID = params.Name, params.Description, params.GroupID
		snapshot.TagIDs = append([]string(nil), params.TagIDs...)
		encoded, _, err := kbgovernance.EncodePublicationSnapshot(snapshot)
		if err != nil {
			return err
		}
		result := tx.Model(&KnowledgeBaseSetupSession{}).
			Where("workspace_id = ? AND knowledge_base_id = ? AND version = ? AND completed_at IS NULL", params.WorkspaceID, params.KnowledgeBaseID, params.ExpectedVersion).
			Updates(map[string]any{"working_snapshot_json": string(encoded), "updated_by": params.ActorID, "updated_at": params.Now, "version": gorm.Expr("version + 1")})
		if result.Error != nil || result.RowsAffected != 1 {
			if result.Error != nil {
				return result.Error
			}
			return ErrOfficialEditConflict
		}
		if strings.TrimSpace(params.AuditEvent.ID) != "" {
			if _, err := s.WithDB(tx).CreateAuditEvent(ctx, params.AuditEvent); err != nil {
				return err
			}
		}
		return tx.Where("workspace_id = ? AND knowledge_base_id = ?", params.WorkspaceID, params.KnowledgeBaseID).First(&updated).Error
	})
	return updated, err
}

// LoadKnowledgeBaseOfficialWorkingSnapshot overlays isolated Basic info onto
// the current bounded Source/artifact state used by the shared setup steps.
func (s *Store) LoadKnowledgeBaseOfficialWorkingSnapshot(ctx context.Context, session KnowledgeBaseSetupSession, lock bool) (PublicationSnapshotResult, error) {
	if err := s.ensureDB(); err != nil {
		return PublicationSnapshotResult{}, err
	}
	var base KnowledgeBase
	query := s.db.WithContext(ctx)
	if lock {
		query = query.Clauses(clause.Locking{Strength: "UPDATE"})
	}
	if err := query.Where("workspace_id = ? AND id = ?", session.WorkspaceID, session.KnowledgeBaseID).First(&base).Error; err != nil {
		return PublicationSnapshotResult{}, err
	}
	current, err := s.loadKnowledgeBasePublicationSnapshot(ctx, base, session.WorkingRevision, lock)
	if err != nil {
		return PublicationSnapshotResult{}, err
	}
	isolated, err := kbgovernance.DecodePublicationSnapshot([]byte(session.WorkingSnapshotJSON))
	if err != nil || isolated.KnowledgeBaseID != base.ID || isolated.WorkingRevision != session.WorkingRevision {
		return PublicationSnapshotResult{}, ErrOfficialEditConflict
	}
	current.Snapshot.Name, current.Snapshot.Description = isolated.Name, isolated.Description
	current.Snapshot.GroupID, current.Snapshot.TagIDs = isolated.GroupID, append([]string(nil), isolated.TagIDs...)
	encoded, digest, err := kbgovernance.EncodePublicationSnapshot(current.Snapshot)
	if err != nil {
		return PublicationSnapshotResult{}, err
	}
	current.JSON, current.Digest = encoded, digest
	return current, nil
}

type SaveKnowledgeBaseOfficialEditParams struct {
	WorkspaceID           string
	KnowledgeBaseID       string
	WorkingRevision       string
	PublishedRevision     string
	ExpectedContentDigest string
	ExpectedVersion       int64
	ClientRequestID       string
	ActorID               string
	AuditEvent            AuditEvent
	Now                   time.Time
}

// SaveKnowledgeBaseOfficialEdit atomically advances the reader-visible
// metadata/revision pointers only after every session and publication fence
// matches. Any failure leaves the previous Official revision unchanged.
func (s *Store) SaveKnowledgeBaseOfficialEdit(ctx context.Context, params SaveKnowledgeBaseOfficialEditParams) (KnowledgeBase, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBase{}, err
	}
	params.WorkspaceID, params.KnowledgeBaseID = strings.TrimSpace(params.WorkspaceID), strings.TrimSpace(params.KnowledgeBaseID)
	params.WorkingRevision, params.PublishedRevision = strings.TrimSpace(params.WorkingRevision), strings.TrimSpace(params.PublishedRevision)
	params.ExpectedContentDigest = strings.TrimSpace(params.ExpectedContentDigest)
	params.ClientRequestID, params.ActorID = strings.TrimSpace(params.ClientRequestID), strings.TrimSpace(params.ActorID)
	if params.WorkspaceID == "" || params.KnowledgeBaseID == "" || params.WorkingRevision == "" || params.PublishedRevision == "" || len(params.ExpectedContentDigest) != 64 || params.ExpectedVersion <= 0 || params.ClientRequestID == "" || params.ActorID == "" || params.Now.IsZero() {
		return KnowledgeBase{}, fmt.Errorf("save official edit: scoped revisions, request, actor, and time are required")
	}
	var saved KnowledgeBase
	var session KnowledgeBaseSetupSession
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("workspace_id = ? AND knowledge_base_id = ?", params.WorkspaceID, params.KnowledgeBaseID).First(&session).Error; err != nil {
			return err
		}
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("workspace_id = ? AND id = ?", params.WorkspaceID, params.KnowledgeBaseID).First(&saved).Error; err != nil {
			return err
		}
		if session.CompletedAt != nil {
			if session.SaveRequestID == params.ClientRequestID && session.PublishedRevision == params.WorkingRevision && saved.PublishedRevision == params.WorkingRevision {
				snapshot, err := kbgovernance.DecodePublicationSnapshot([]byte(session.PublishedSnapshotJSON))
				if err != nil {
					return ErrOfficialEditReplayConflict
				}
				_, digest, err := kbgovernance.EncodePublicationSnapshot(snapshot)
				if err != nil || digest != params.ExpectedContentDigest {
					return ErrOfficialEditReplayConflict
				}
				return nil
			}
			return ErrOfficialEditReplayConflict
		}
		if session.Mode != SetupModeEditOfficial || session.Version != params.ExpectedVersion || session.WorkingRevision != params.WorkingRevision || session.PublishedRevision != params.PublishedRevision ||
			!saved.IsActive || saved.PublicationState != "official" || saved.PublishedRevision != params.PublishedRevision || saved.WorkingRevision != params.WorkingRevision {
			return ErrOfficialEditConflict
		}
		working, err := s.WithDB(tx).LoadKnowledgeBaseOfficialWorkingSnapshot(ctx, session, true)
		if err != nil {
			return err
		}
		if working.Digest != params.ExpectedContentDigest {
			return ErrOfficialEditConflict
		}
		var group KnowledgeBaseGroup
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("workspace_id = ? AND id = ?", params.WorkspaceID, working.Snapshot.GroupID).
			First(&group).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return ErrOfficialEditConflict
			}
			return err
		}
		if len(working.Snapshot.TagIDs) > 0 {
			var tags []KnowledgeBaseTag
			if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
				Where("workspace_id = ? AND id IN ?", params.WorkspaceID, working.Snapshot.TagIDs).
				Find(&tags).Error; err != nil {
				return err
			}
			if len(tags) != len(working.Snapshot.TagIDs) {
				return ErrOfficialEditConflict
			}
		}
		saved.Name, saved.Description, saved.GroupID = working.Snapshot.Name, working.Snapshot.Description, working.Snapshot.GroupID
		saved.PublishedRevision, saved.WorkingRevision, saved.PublishedAt = params.WorkingRevision, "", &params.Now
		saved.UpdatedBy, saved.UpdatedAt = params.ActorID, params.Now
		result := tx.Model(&KnowledgeBase{}).
			Where("workspace_id = ? AND id = ? AND publication_state = 'official' AND published_revision = ? AND working_revision = ?", params.WorkspaceID, params.KnowledgeBaseID, params.PublishedRevision, params.WorkingRevision).
			Updates(map[string]any{
				"name": saved.Name, "description": saved.Description, "group_id": saved.GroupID,
				"published_revision": saved.PublishedRevision, "working_revision": "", "published_at": params.Now,
				"updated_by": params.ActorID, "updated_at": params.Now,
			})
		if result.Error != nil || result.RowsAffected != 1 {
			if result.Error != nil {
				return result.Error
			}
			return ErrOfficialEditConflict
		}
		if err := s.WithDB(tx).ReplaceKnowledgeBaseTags(ctx, params.WorkspaceID, params.KnowledgeBaseID, params.ActorID, working.Snapshot.TagIDs, params.Now); err != nil {
			return err
		}
		result = tx.Model(&KnowledgeBaseSetupSession{}).
			Where("workspace_id = ? AND knowledge_base_id = ? AND version = ? AND completed_at IS NULL", params.WorkspaceID, params.KnowledgeBaseID, params.ExpectedVersion).
			Updates(map[string]any{
				"published_revision": params.WorkingRevision, "published_snapshot_json": string(working.JSON),
				"working_snapshot_json": string(working.JSON), "save_request_id": params.ClientRequestID,
				"completed_at": params.Now, "updated_by": params.ActorID, "updated_at": params.Now,
				"version": gorm.Expr("version + 1"),
			})
		if result.Error != nil || result.RowsAffected != 1 {
			if result.Error != nil {
				return result.Error
			}
			return ErrOfficialEditConflict
		}
		if strings.TrimSpace(params.AuditEvent.ID) != "" {
			if _, err := s.WithDB(tx).CreateAuditEvent(ctx, params.AuditEvent); err != nil {
				return err
			}
		}
		if err := tx.Where("workspace_id = ? AND id = ?", params.WorkspaceID, params.KnowledgeBaseID).First(&saved).Error; err != nil {
			return err
		}
		return nil
	})
	return saved, err
}
