package skills

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

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/service/resource/access"
	"github.com/tidbcloud/AgentTeam/internal/service/resource/repos"
	reposstore "github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
	store "github.com/tidbcloud/AgentTeam/internal/store/resource/skills"
	"gorm.io/gorm"
)

func checkedSkillPageOffset(page, perPage, maxPerPage, maxOffset int) (int, bool) {
	if page <= 0 || perPage <= 0 || (maxPerPage > 0 && perPage > maxPerPage) || maxOffset < 0 {
		return 0, false
	}
	pageIndex := page - 1
	if pageIndex > maxOffset/perPage {
		return 0, false
	}
	offset := pageIndex * perPage
	if offset < 0 || offset > maxOffset || offset > math.MaxInt-perPage {
		return 0, false
	}
	return offset, true
}

func (s *Service) createSkillSyncToken(ctx context.Context, workspaceID, subject string, agents map[string][]AgentAccessibleSkill) (repos.RepositorySyncTokenLease, error) {
	if s.syncTokenIssuer == nil {
		return repos.RepositorySyncTokenLease{}, nil
	}
	repositories := repositoryFullNamesFromSkills(agents)
	if len(repositories) == 0 {
		return repos.RepositorySyncTokenLease{}, nil
	}
	request := access.NewSyncLeaseRequest(workspaceID, subject, repos.RepositorySyncTokenDefaultProviderHost, "Skill sync lease", repositories, []string{
		repos.RepositorySyncCapabilityRead,
		repos.RepositorySyncCapabilityClone,
	})
	return s.syncTokenIssuer.CreateRepositorySyncToken(ctx, repos.RepositorySyncTokenRequest{
		WorkspaceID:      request.WorkspaceID,
		ActorPrincipalID: request.ActorPrincipalID,
		ProviderHost:     request.ProviderHost,
		Repositories:     request.Repositories,
		Capabilities:     request.Capabilities,
		DisplayName:      request.DisplayName,
	})
}

func repositoryFullNamesFromSkills(agents map[string][]AgentAccessibleSkill) []string {
	fullNames := make([]string, 0, len(agents))
	for _, skills := range agents {
		for _, skill := range skills {
			if skill.Repository == nil {
				continue
			}
			fullNames = append(fullNames, skill.Repository.RepoFullName)
		}
	}
	return access.UniqueRepositoryFullNames(fullNames)
}

func agentAccessibleSkillFromRow(row store.AgentAccessibleSkill, repository *reposstore.ResourceRepository, native bool) AgentAccessibleSkill {
	result := AgentAccessibleSkill{ID: row.Skill.ID, WorkspaceID: row.Skill.WorkspaceID, Name: row.Skill.Name, Description: row.Skill.Description, PermissionSetAt: row.Binding.UpdatedAt, UpdatedAt: row.Skill.UpdatedAt}
	if native {
		result.ContentID = row.Skill.ContentID
		result.ContentVersionID = row.Skill.ActiveContentVersionID
		return result
	}
	result.Repository = repository
	result.Branch = row.Binding.Branch
	return result
}

func agentAccessibleOfficialSkill(skill store.Skill, repository *reposstore.ResourceRepository, native bool) AgentAccessibleSkill {
	result := AgentAccessibleSkill{ID: skill.ID, WorkspaceID: skill.WorkspaceID, Name: skill.Name, Description: skill.Description, PermissionSetAt: skill.UpdatedAt, UpdatedAt: skill.UpdatedAt}
	if native {
		result.ContentID = skill.ContentID
		result.ContentVersionID = skill.ActiveContentVersionID
		return result
	}
	result.Repository = repository
	result.Branch = firstNonEmpty(repository.DefaultRef, "main")
	return result
}

func uniqueNonEmptyStrings(values []string) []string {
	out := make([]string, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		out = append(out, value)
	}
	return out
}

func validateSubject(subject string) error {
	if strings.TrimSpace(subject) == "" {
		return unauthorized("Missing authenticated identity")
	}
	return nil
}

func mapStoreError(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return notFound("skill not found")
	}
	if errors.Is(err, store.ErrRepositoryBranchConflict) {
		return conflict(err.Error())
	}
	if errors.Is(err, store.ErrSkillSlugConflict) {
		return conflict("skill already exists for this visibility scope")
	}
	return err
}

var skillSlugPattern = regexp.MustCompile(`[^a-z0-9]+`)

func skillSlug(value string) string {
	value = strings.ToLower(strings.TrimSpace(value))
	value = skillSlugPattern.ReplaceAllString(value, "-")
	value = strings.Trim(value, "-")
	if len(value) > 80 {
		value = strings.Trim(value[:80], "-")
	}
	return value
}

func skillRepositoryName(slug string) string {
	name := skillSlug(slug)
	return name + ".sk"
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}

func normalizeSkillListView(view string) string {
	switch strings.TrimSpace(view) {
	case "created_by_me":
		return "created_by_me"
	case "official":
		return "official"
	case "shared":
		return "shared"
	default:
		return ""
	}
}

func normalizeSkillRepositoryVisibility(visibility string) (string, error) {
	switch strings.ToLower(strings.TrimSpace(visibility)) {
	case "", "private":
		return "private", nil
	case "public":
		return "public", nil
	default:
		return "", invalidArgument("skill repository visibility must be public or private")
	}
}

func resolveSkillCreateVisibility(workspaceID, subject string, input SkillCreateMutation) (resolvedSkillCreateVisibility, error) {
	workspaceID = strings.TrimSpace(workspaceID)
	subject = strings.TrimSpace(subject)
	storageScope := strings.TrimSpace(input.StorageScope)
	if storageScope == "" {
		storageScope = repos.RepositoryStorageScopePersonal
	}
	switch strings.TrimSpace(input.VisibilityScope) {
	case SkillVisibilityScopeWorkspace:
		if workspaceID == "" {
			return resolvedSkillCreateVisibility{}, invalidArgument("workspace id is required")
		}
		return resolvedSkillCreateVisibility{
			VisibilityScope:   SkillVisibilityScopeWorkspace,
			VisibilityOwnerID: workspaceID,
			StorageScope:      storageScope,
		}, nil
	case SkillVisibilityScopePrivate:
		if subject == "" {
			return resolvedSkillCreateVisibility{}, unauthorized("Missing authenticated identity")
		}
		return resolvedSkillCreateVisibility{
			VisibilityScope:   SkillVisibilityScopePrivate,
			VisibilityOwnerID: subject,
			StorageScope:      storageScope,
		}, nil
	case "":
		if subject == "" {
			return resolvedSkillCreateVisibility{}, unauthorized("Missing authenticated identity")
		}
		return resolvedSkillCreateVisibility{
			VisibilityScope:   SkillVisibilityScopePrivate,
			VisibilityOwnerID: subject,
			StorageScope:      storageScope,
		}, nil
	default:
		return resolvedSkillCreateVisibility{}, invalidArgument("skill visibility scope must be private or workspace")
	}
}

func resolveSkillUpdateVisibility(workspaceID, subject string, skill store.Skill, requested string) (string, string, error) {
	workspaceID = strings.TrimSpace(workspaceID)
	subject = strings.TrimSpace(subject)
	switch strings.TrimSpace(requested) {
	case SkillVisibilityScopeWorkspace:
		if workspaceID == "" {
			return "", "", invalidArgument("workspace id is required")
		}
		return SkillVisibilityScopeWorkspace, workspaceID, nil
	case SkillVisibilityScopePrivate:
		ownerID := strings.TrimSpace(skill.CreatedBy)
		if ownerID == "" {
			ownerID = subject
		}
		if ownerID == "" {
			return "", "", unauthorized("Missing authenticated identity")
		}
		return SkillVisibilityScopePrivate, ownerID, nil
	default:
		return "", "", invalidArgument("skill visibility scope must be private or workspace")
	}
}

func boolPointer(value bool) *bool {
	return &value
}

func skillImportCommitMessage(input SkillContentImportMutation) string {
	if sourceURL := strings.TrimSpace(input.SourceURL); sourceURL != "" {
		source := sourceURL
		if strings.Contains(source, "?") {
			source = strings.SplitN(source, "?", 2)[0]
		}
		if len(source) > 160 {
			source = source[:160]
		}
		return "Import skills from " + source
	}
	sourceName := strings.TrimSpace(input.SourceName)
	if sourceName == "" {
		sourceName = "uploaded archive"
	}
	return "Import skills from " + sourceName
}

func newSkillID() string {
	return newSkillPrefixedID("sk")
}

func newSkillResourceRepositoryID() string {
	return newSkillPrefixedID("rr")
}

func newSkillAgentBindingID() string {
	return newSkillPrefixedID("sab")
}

func newSkillRepositoryBranchID() string {
	return newSkillPrefixedID("srb")
}

func newSkillGrantID() string {
	return newSkillPrefixedID("skg_")
}

func newSkillPrefixedID(prefix string) string {
	id, err := identifiers.NewEntityID(prefix)
	if err != nil {
		return fmt.Sprintf("%s_%d", prefix, time.Now().UnixNano())
	}
	return id
}
