package kb

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

	"github.com/tidbcloud/AgentTeam/internal/service/providertokens"
	storekb "github.com/tidbcloud/AgentTeam/internal/store/kb"
	providertokensstore "github.com/tidbcloud/AgentTeam/internal/store/providertokens"
	"gorm.io/gorm"
)

func validatePersonalAccessTokenCredentialMetadata(token providertokensstore.PersonalAccessToken, remoteURL string, now time.Time) error {
	if strings.TrimSpace(token.Status) != providertokens.StatusActive {
		return invalidArgument("source mirror personal access token is not active")
	}
	if token.ExpiresAt != nil && !token.ExpiresAt.After(now) {
		return invalidArgument("source mirror personal access token is expired")
	}
	if !providertokens.ContainsUsage(providertokens.DecodeAllowedUsages(token.AllowedUsagesJSON), providertokens.UsageKBMirror) {
		return invalidArgument("source mirror personal access token is not allowed for KB mirror usage")
	}
	if strings.TrimSpace(remoteURL) != "" {
		matches, err := providertokens.RemoteURLMatchesProviderBaseURL(remoteURL, token.ProviderBaseURL)
		if err != nil {
			return mapPersonalAccessTokenError(err)
		}
		if !matches {
			return invalidArgument("source mirror personal access token provider base URL does not match the remote URL")
		}
	}
	return nil
}

func (s *KBService) validatePersonalAccessTokenForMirrorMutation(ctx context.Context, source storekb.Source, mirror storekb.SourceMirror, subject string, requireOwner bool) error {
	token, ok, err := s.personalAccessTokenForCredentialRef(ctx, mirror.WorkspaceID, mirror.CredentialRef)
	if err != nil || !ok {
		return err
	}
	if requireOwner && strings.TrimSpace(token.OwnerPrincipalID) != strings.TrimSpace(subject) {
		return forbidden("source mirror personal access token must be owned by the current user")
	}
	if err := validatePersonalAccessTokenCredentialMetadata(token, mirror.RemoteURL, s.now()); err != nil {
		return err
	}
	return s.validatePersonalAccessTokenOwnerCanSyncSource(ctx, source.WorkspaceID, source.KnowledgeBaseID, token.OwnerPrincipalID)
}

func (s *KBService) validatePersonalAccessTokenForMirrorSync(ctx context.Context, mirror storekb.SourceMirror) error {
	token, ok, err := s.personalAccessTokenForCredentialRef(ctx, mirror.WorkspaceID, mirror.CredentialRef)
	if err != nil || !ok {
		return err
	}
	if err := validatePersonalAccessTokenCredentialMetadata(token, mirror.RemoteURL, s.now()); err != nil {
		return err
	}
	return s.validatePersonalAccessTokenOwnerCanSyncSource(ctx, mirror.WorkspaceID, mirror.KnowledgeBaseID, token.OwnerPrincipalID)
}

func (s *KBService) validatePersonalAccessTokenOwnerCanSyncSource(ctx context.Context, workspaceID, knowledgeBaseID, ownerPrincipalID string) error {
	if strings.TrimSpace(ownerPrincipalID) == "" {
		return invalidArgument("source mirror personal access token owner is empty")
	}
	if s.principalWorkspaceAccessVerifier == nil {
		return forbidden("source mirror personal access token owner workspace access cannot be verified")
	}
	active, err := s.principalWorkspaceAccessVerifier.HasWorkspaceAccess(ctx, workspaceID, ownerPrincipalID)
	if err != nil {
		return fmt.Errorf("verify source mirror personal access token owner workspace access: %w", err)
	}
	if !active {
		return forbidden("source mirror personal access token owner no longer has workspace access")
	}
	base, err := s.store.GetKnowledgeBase(ctx, workspaceID, knowledgeBaseID)
	if err != nil {
		return mapStoreNotFound(err)
	}
	if !roleCanEdit(s.knowledgeBaseRole(ctx, base, ownerPrincipalID)) {
		return forbidden("source mirror personal access token owner no longer has knowledge base editor access")
	}
	return nil
}

func (s *KBService) personalAccessTokenForCredentialRef(ctx context.Context, workspaceID, credentialRef string) (providertokensstore.PersonalAccessToken, bool, error) {
	tokenID, ok := providertokens.IDFromCredentialRef(credentialRef)
	if !ok {
		return providertokensstore.PersonalAccessToken{}, false, nil
	}
	if s.personalAccessTokenStore == nil {
		return providertokensstore.PersonalAccessToken{}, true, invalidArgument("Personal access token storage is not configured on server")
	}
	token, err := s.personalAccessTokenStore.Get(ctx, workspaceID, tokenID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return providertokensstore.PersonalAccessToken{}, true, invalidArgument("source mirror personal access token was not found")
		}
		return providertokensstore.PersonalAccessToken{}, true, fmt.Errorf("load source mirror personal access token: %w", err)
	}
	return token, true, nil
}

func (s *KBService) decorateSourceMirrorCredentialSummaries(ctx context.Context, mirrors []*storekb.SourceMirror) {
	if len(mirrors) == 0 {
		return
	}
	personalIDs := make([]string, 0, len(mirrors))
	refs := make([]string, 0, len(mirrors))
	for _, mirror := range mirrors {
		if mirror == nil {
			continue
		}
		if tokenID, ok := providertokens.IDFromCredentialRef(mirror.CredentialRef); ok {
			personalIDs = append(personalIDs, tokenID)
			refs = append(refs, providertokens.CredentialRef(tokenID))
			continue
		}
		if strings.TrimSpace(mirror.CredentialRef) != "" {
			mirror.CredentialSummary = &storekb.SourceMirrorCredentialSummary{Kind: "automation_secret", Status: providertokens.StatusActive}
		}
	}
	if len(personalIDs) == 0 || s.personalAccessTokenStore == nil {
		return
	}
	workspaceID := strings.TrimSpace(mirrors[0].WorkspaceID)
	tokens, err := s.personalAccessTokenStore.ListMetadataByIDs(ctx, workspaceID, personalIDs)
	if err != nil {
		return
	}
	counts, err := s.store.CountSourceMirrorsByCredentialRefs(ctx, workspaceID, refs)
	if err != nil {
		return
	}
	byID := make(map[string]providertokensstore.PersonalAccessToken, len(tokens))
	for _, token := range tokens {
		byID[token.ID] = token
	}
	now := s.now()
	for _, mirror := range mirrors {
		if mirror == nil {
			continue
		}
		tokenID, ok := providertokens.IDFromCredentialRef(mirror.CredentialRef)
		if !ok {
			continue
		}
		token, ok := byID[tokenID]
		if !ok {
			mirror.CredentialSummary = &storekb.SourceMirrorCredentialSummary{Kind: "personal_access_token", Status: "missing"}
			continue
		}
		status := token.Status
		if status == providertokens.StatusActive && token.ExpiresAt != nil && !token.ExpiresAt.After(now) {
			status = providertokens.StatusExpired
		}
		mirror.CredentialSummary = &storekb.SourceMirrorCredentialSummary{
			Kind:              "personal_access_token",
			DisplayName:       token.DisplayName,
			OwnerPrincipalID:  token.OwnerPrincipalID,
			ProviderType:      token.ProviderType,
			ProviderBaseURL:   token.ProviderBaseURL,
			Status:            status,
			LastUsedAt:        token.LastUsedAt,
			ExpiresAt:         token.ExpiresAt,
			LinkedSourceCount: counts[providertokens.CredentialRef(token.ID)],
		}
	}
}

func mapPersonalAccessTokenError(err error) error {
	switch {
	case errors.Is(err, providertokens.ErrPermissionDenied):
		return forbidden(err.Error())
	case errors.Is(err, providertokens.ErrTokenNotFound):
		return notFound(err.Error())
	case errors.Is(err, providertokens.ErrTokenStateConflict):
		return conflict(err.Error())
	case errors.Is(err, providertokens.ErrInvalidRequest), errors.Is(err, providertokens.ErrStorageUnavailable):
		return invalidArgument(err.Error())
	default:
		return err
	}
}
