package skills

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

	store "github.com/tidbcloud/AgentTeam/internal/store/resource/skills"
)

type SkillVersionPage struct {
	Items   []SkillVersion
	Page    int
	PerPage int
	HasMore bool
}

// SkillVersion is the Skill-domain projection of one immutable ContentVersion.
// Persistence details remain owned by internal/store/resource/skills.
type SkillVersion struct {
	ID               string
	ContentID        string
	ContentVersionID string
	RootDigest       string
	FileCount        int
	SizeBytes        int64
	SkillCount       int
	CreatedBy        string
	CreatedAt        time.Time
}

func (s *Service) ListSkillVersions(ctx context.Context, workspaceID, subject, skillID string, page, perPage int) (SkillVersionPage, error) {
	offset, ok := checkedSkillPageOffset(page, perPage, 100, 10_000)
	if !ok {
		return SkillVersionPage{}, invalidArgument("skill version page is out of bounds")
	}
	skill, err := s.GetSkill(ctx, workspaceID, subject, skillID)
	if err != nil {
		return SkillVersionPage{}, err
	}
	native, err := s.nativeSkillRead(ctx, skill.WorkspaceID, skill.ID)
	if err != nil {
		return SkillVersionPage{}, err
	}
	if !native {
		return SkillVersionPage{}, invalidArgument("native skill version history is not active")
	}
	rows, err := s.store.ListSkillVersions(ctx, skill.WorkspaceID, skill.ID, perPage, offset)
	if err != nil {
		return SkillVersionPage{}, err
	}
	hasMore := len(rows) > perPage
	if hasMore {
		rows = rows[:perPage]
	}
	items := make([]SkillVersion, 0, len(rows))
	for _, row := range rows {
		items = append(items, skillVersionFromStore(row))
	}
	return SkillVersionPage{Items: items, Page: page, PerPage: perPage, HasMore: hasMore}, nil
}

func (s *Service) RollbackSkillVersion(ctx context.Context, workspaceID, subject, skillID, skillVersionID string) (output SkillVersion, err error) {
	skill, err := s.GetSkill(ctx, workspaceID, subject, skillID)
	if err != nil {
		return SkillVersion{}, err
	}
	if !skillRoleCanManage(skill.Role) {
		return SkillVersion{}, forbidden("skill requires manage access")
	}
	writeRoute, err := s.beginSkillWrite(ctx, skill.WorkspaceID, skill.ID)
	if err != nil {
		return SkillVersion{}, err
	}
	if !writeRoute.native {
		return SkillVersion{}, invalidArgument("native skill rollback is not active")
	}
	target, err := s.store.GetSkillVersion(ctx, skill.WorkspaceID, skill.ID, strings.TrimSpace(skillVersionID))
	if err != nil {
		return SkillVersion{}, mapStoreError(err)
	}
	if target.ContentID != skill.ContentID {
		return SkillVersion{}, fmt.Errorf("skill version content binding does not match active lineage")
	}
	result, err := s.nativeContent.Restore(ctx, nativeSkillRequest(skill.ID, skill.WorkspaceID, skill.ContentID, skill.ActiveContentVersionID, subject), target.ContentVersionID)
	if err != nil {
		return SkillVersion{}, err
	}
	_, created, err := s.finalizeNativeSkillVersion(ctx, skill, strings.TrimSpace(subject), result, skillActivityBackupRestored, "version", "", map[string]any{
		"versionId": result.VersionID, "rollbackFromSkillVersionId": target.ID,
	}, "")
	if err != nil {
		return SkillVersion{}, err
	}
	return skillVersionFromStore(created), nil
}

// finalizeNativeSkillVersion is the single product-transaction owner shared by
// import, backup restore, and history rollback. Content Store publication is
// external to this transaction, so retries first recognize an already
// committed exact projection and otherwise advance the Skill with a CAS. An
// official-source fingerprint is finalized here as part of the same product
// transaction so receipt replay cannot publish a duplicate ContentVersion.
func (s *Service) finalizeNativeSkillVersion(ctx context.Context, skill store.Skill, subject string, result NativeSkillVersionResult, eventType, targetType, targetID string, details map[string]any, officialSourceFingerprint string) (store.Skill, store.SkillVersion, error) {
	now := s.now()
	officialSourceFingerprint = strings.TrimSpace(officialSourceFingerprint)
	version := store.SkillVersion{
		ID: newSkillPrefixedID("skv"), WorkspaceID: skill.WorkspaceID, SkillID: skill.ID,
		ContentID: result.ContentID, ContentVersionID: result.VersionID, RootDigest: result.RootDigest,
		FileCount: result.FileCount, SizeBytes: result.SizeBytes, SkillCount: result.SkillCount,
		CreatedBy: strings.TrimSpace(subject), CreatedAt: now,
	}
	var updated store.Skill
	var projected store.SkillVersion
	err := s.store.Transaction(ctx, func(tx *store.Store) error {
		existing, found, err := tx.FindSkillVersionByContentVersion(ctx, skill.WorkspaceID, skill.ID, result.ContentID, result.VersionID)
		if err != nil {
			return err
		}
		if found {
			if existing.RootDigest != result.RootDigest || existing.FileCount != result.FileCount || existing.SizeBytes != result.SizeBytes || existing.SkillCount != result.SkillCount {
				return store.ErrSkillVersionConflict
			}
			current, err := tx.GetSkill(ctx, skill.WorkspaceID, skill.ID)
			if err != nil {
				return err
			}
			if current.ContentID != result.ContentID || current.ActiveContentVersionID != result.VersionID {
				return store.ErrSkillVersionConflict
			}
			updated, projected = current, existing
			if officialSourceFingerprint != "" && current.OfficialSourceFingerprint != officialSourceFingerprint {
				updated, err = tx.UpdateOfficialSourceFingerprint(ctx, skill.WorkspaceID, skill.ID, officialSourceFingerprint, subject, now)
				return err
			}
			return nil
		}
		updated, err = tx.AdvanceSkillContentVersion(ctx, skill.WorkspaceID, skill.ID, skill.ContentID, skill.ActiveContentVersionID, result.ContentID, result.VersionID, result.SkillCount, subject, now)
		if err != nil {
			return err
		}
		projected, err = tx.CreateSkillVersion(ctx, version)
		if err != nil {
			return err
		}
		activityTargetID := strings.TrimSpace(targetID)
		if activityTargetID == "" {
			activityTargetID = projected.ID
		}
		if strings.TrimSpace(eventType) != "" {
			if err := writeSkillActivity(ctx, s.skillActivityStore(tx), updated, "contentstore://"+result.ContentID+"/"+result.VersionID, eventType, subject, targetType, activityTargetID, details, now); err != nil {
				return err
			}
		}
		if officialSourceFingerprint != "" {
			updated, err = tx.UpdateOfficialSourceFingerprint(ctx, skill.WorkspaceID, skill.ID, officialSourceFingerprint, subject, now)
			return err
		}
		return nil
	})
	if err != nil {
		return store.Skill{}, store.SkillVersion{}, mapStoreError(err)
	}
	return updated, projected, nil
}

func skillVersionFromStore(version store.SkillVersion) SkillVersion {
	return SkillVersion{
		ID: version.ID, ContentID: version.ContentID, ContentVersionID: version.ContentVersionID,
		RootDigest: version.RootDigest, FileCount: version.FileCount, SizeBytes: version.SizeBytes,
		SkillCount: version.SkillCount, CreatedBy: version.CreatedBy, CreatedAt: version.CreatedAt,
	}
}
