package skills

import (
	"context"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/store/content"
)

type skillAuthorityReader interface {
	GetAuthority(context.Context, string, string, string) (content.Authority, bool, error)
	ListAuthoritiesForBindings(context.Context, string, string, []string) (map[string]content.Authority, error)
}

type skillWriteRoute struct {
	native     bool
	generation uint64
}

func (s *Service) beginSkillWrite(ctx context.Context, workspaceID, skillID string) (skillWriteRoute, error) {
	if s.contentAuthority == nil {
		return skillWriteRoute{}, nil
	}
	authority, found, err := s.contentAuthority.GetAuthority(ctx, workspaceID, skillContentDomain, skillID)
	if err != nil {
		return skillWriteRoute{}, fmt.Errorf("resolve skill content authority: %w", err)
	}
	if !found {
		return skillWriteRoute{generation: 0}, nil
	}
	if strings.TrimSpace(authority.State) == content.AuthorityStateLegacyActive {
		return skillWriteRoute{generation: authority.Generation}, nil
	}
	switch strings.TrimSpace(authority.State) {
	case content.AuthorityStateNativeActive:
		if !s.nativeContentEnabled || s.nativeContent == nil {
			return skillWriteRoute{}, fmt.Errorf("native skill content is unavailable")
		}
		return skillWriteRoute{native: true, generation: authority.Generation}, nil
	default:
		return skillWriteRoute{}, fmt.Errorf("unknown skill content authority state %q", authority.State)
	}
}

// ensureSkillWriteCurrent closes the authority-check race around legacy
// mutations. Callers capture the exact generation before doing any external
// work, then re-check immediately before committing a product projection. A
// concurrent one-way activation therefore fences the stale legacy writer
// instead of allowing it to land after native ownership became active.
func (s *Service) ensureSkillWriteCurrent(ctx context.Context, workspaceID, skillID string, route skillWriteRoute) error {
	if s.contentAuthority == nil {
		return nil
	}
	authority, found, err := s.contentAuthority.GetAuthority(ctx, workspaceID, skillContentDomain, skillID)
	if err != nil {
		return fmt.Errorf("recheck skill content authority: %w", err)
	}
	// Implicit legacy bindings intentionally remain compatible with the
	// one-way cutover contract: an activation may overtake an in-flight AGS
	// operation that started before a row existed. Persisted legacy rows carry
	// a generation and are fenced below.
	if route.generation == 0 && !found {
		return nil
	}
	state := content.AuthorityStateLegacyActive
	if found {
		state = strings.TrimSpace(authority.State)
	}
	want := content.AuthorityStateLegacyActive
	if route.native {
		want = content.AuthorityStateNativeActive
	}
	if state != want || (found && authority.Generation != route.generation) || (!found && route.generation != 0) {
		return conflict("skill content authority changed during mutation")
	}
	if route.native && (!s.nativeContentEnabled || s.nativeContent == nil) {
		return fmt.Errorf("native skill content is unavailable")
	}
	return nil
}

func (s *Service) nativeSkillRead(ctx context.Context, workspaceID, skillID string) (bool, error) {
	if s.contentAuthority == nil {
		return false, nil
	}
	authority, found, err := s.contentAuthority.GetAuthority(ctx, workspaceID, skillContentDomain, skillID)
	if err != nil {
		return false, fmt.Errorf("resolve skill content authority: %w", err)
	}
	state := content.AuthorityStateLegacyActive
	if found {
		state = strings.TrimSpace(authority.State)
	}
	switch state {
	case content.AuthorityStateLegacyActive:
		return false, nil
	case content.AuthorityStateNativeActive:
		if !s.nativeContentEnabled || s.nativeContent == nil {
			return false, fmt.Errorf("native skill content is unavailable")
		}
		return true, nil
	default:
		return false, fmt.Errorf("unknown skill content authority state %q", state)
	}
}

func (s *Service) nativeSkillReads(ctx context.Context, workspaceID string, skillIDs []string) (map[string]bool, error) {
	out := make(map[string]bool, len(skillIDs))
	if s.contentAuthority == nil || len(skillIDs) == 0 {
		return out, nil
	}
	authorities, err := s.contentAuthority.ListAuthoritiesForBindings(ctx, workspaceID, skillContentDomain, skillIDs)
	if err != nil {
		return nil, fmt.Errorf("resolve skill content authorities: %w", err)
	}
	for _, skillID := range skillIDs {
		skillID = strings.TrimSpace(skillID)
		authority, found := authorities[skillID]
		if !found {
			continue
		}
		switch strings.TrimSpace(authority.State) {
		case content.AuthorityStateLegacyActive:
			continue
		case content.AuthorityStateNativeActive:
			if !s.nativeContentEnabled || s.nativeContent == nil {
				return nil, fmt.Errorf("native skill content is unavailable")
			}
			out[skillID] = true
		default:
			return nil, fmt.Errorf("unknown skill content authority state %q", authority.State)
		}
	}
	return out, nil
}

func nativeSkillRequest(skillID, workspaceID, contentID, versionID, actor string) NativeSkillContentRequest {
	return NativeSkillContentRequest{
		WorkspaceID: strings.TrimSpace(workspaceID), SkillID: strings.TrimSpace(skillID),
		ContentID: strings.TrimSpace(contentID), VersionID: strings.TrimSpace(versionID), Actor: strings.TrimSpace(actor),
	}
}
