package kb

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

	"github.com/tidbcloud/AgentTeam/internal/automation/dsl"
	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/service/providertokens"
	"github.com/tidbcloud/AgentTeam/internal/store/automation"
	providertokensstore "github.com/tidbcloud/AgentTeam/internal/store/providertokens"
	"gorm.io/gorm"
)

type SourceMirrorCredentialResolverSet struct {
	Automation SourceMirrorCredentialResolver
	Personal   SourceMirrorCredentialResolver
}

func (r SourceMirrorCredentialResolverSet) ResolveSourceMirrorCredential(ctx context.Context, request SourceMirrorCredentialRequest) (SourceMirrorCredential, error) {
	if _, ok := providertokens.IDFromCredentialRef(request.CredentialRef); ok {
		if r.Personal == nil {
			return SourceMirrorCredential{}, invalidArgument("source mirror personal access token resolver is not configured")
		}
		return r.Personal.ResolveSourceMirrorCredential(ctx, request)
	}
	if r.Automation == nil {
		return SourceMirrorCredential{}, invalidArgument("source mirror automation secret resolver is not configured")
	}
	return r.Automation.ResolveSourceMirrorCredential(ctx, request)
}

// AutomationSecretSourceMirrorCredentialResolver resolves source mirror tokens
// from the repo-scoped automation secret store without exposing plaintext to job state.
type AutomationSecretSourceMirrorCredentialResolver struct {
	Store          *automation.Store
	CredentialsKey []byte
}

func (r AutomationSecretSourceMirrorCredentialResolver) ResolveSourceMirrorCredential(ctx context.Context, request SourceMirrorCredentialRequest) (SourceMirrorCredential, error) {
	if r.Store == nil {
		return SourceMirrorCredential{}, invalidArgument("source mirror automation secret store is not configured")
	}
	codec, err := secrets.NewAESGCMCodec(r.CredentialsKey)
	if err != nil {
		return SourceMirrorCredential{}, invalidArgument("Automation secret storage is not configured on server")
	}
	workspaceID := strings.TrimSpace(request.WorkspaceID)
	owner := strings.TrimSpace(request.RepositoryOwner)
	repo := strings.TrimSpace(request.RepositoryName)
	name, err := parseSourceMirrorAutomationSecretRef(request.CredentialRef, owner, repo)
	if err != nil {
		return SourceMirrorCredential{}, err
	}
	secret, err := r.Store.GetAutomationSecret(ctx, workspaceID, owner, repo, name)
	if err != nil {
		return SourceMirrorCredential{}, fmt.Errorf("load source mirror automation secret %s/%s/%s: %w", owner, repo, name, err)
	}
	plaintext, err := codec.Open(secret.ValueCiphertext)
	if err != nil {
		return SourceMirrorCredential{}, fmt.Errorf("decrypt source mirror automation secret %s/%s/%s: %w", owner, repo, name, err)
	}
	plaintext = strings.TrimSpace(plaintext)
	if plaintext == "" {
		return SourceMirrorCredential{}, invalidArgument("source mirror automation secret value is empty")
	}
	return SourceMirrorCredential{Username: "x-access-token", Password: plaintext}, nil
}

// PersonalAccessTokenSourceMirrorCredentialResolver resolves source mirror tokens
// from user-owned personal external access tokens. It returns only process-local
// credentials and never exposes ciphertext or plaintext to job state.
type PersonalAccessTokenSourceMirrorCredentialResolver struct {
	Store          *providertokensstore.Store
	CredentialsKey []byte
	Now            func() time.Time
}

func (r PersonalAccessTokenSourceMirrorCredentialResolver) ResolveSourceMirrorCredential(ctx context.Context, request SourceMirrorCredentialRequest) (SourceMirrorCredential, error) {
	if r.Store == nil {
		return SourceMirrorCredential{}, invalidArgument("source mirror personal access token store is not configured")
	}
	codec, err := secrets.NewAESGCMCodec(r.CredentialsKey)
	if err != nil {
		return SourceMirrorCredential{}, invalidArgument("Personal access token credential storage is not configured on server")
	}
	tokenID, ok := providertokens.IDFromCredentialRef(request.CredentialRef)
	if !ok {
		return SourceMirrorCredential{}, invalidArgument("source mirror credential_ref must reference a personal access token")
	}
	token, err := r.Store.Get(ctx, request.WorkspaceID, tokenID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return SourceMirrorCredential{}, invalidArgument("source mirror personal access token was not found")
		}
		return SourceMirrorCredential{}, fmt.Errorf("load source mirror personal access token: %w", err)
	}
	if err := validatePersonalAccessTokenCredentialMetadata(token, request.RemoteURL, r.now()); err != nil {
		return SourceMirrorCredential{}, err
	}
	plaintext, err := codec.Open(token.ValueCiphertext)
	if err != nil {
		return SourceMirrorCredential{}, fmt.Errorf("decrypt source mirror personal access token: %w", err)
	}
	plaintext = strings.TrimSpace(plaintext)
	if plaintext == "" {
		return SourceMirrorCredential{}, invalidArgument("source mirror personal access token value is empty")
	}
	if err := r.Store.TouchLastUsed(ctx, token.WorkspaceID, token.ID, r.now()); err != nil {
		return SourceMirrorCredential{}, fmt.Errorf("update source mirror personal access token usage: %w", err)
	}
	username := strings.TrimSpace(token.Username)
	if username == "" {
		username = "x-access-token"
	}
	return SourceMirrorCredential{Username: username, Password: plaintext}, nil
}

func (r PersonalAccessTokenSourceMirrorCredentialResolver) now() time.Time {
	if r.Now != nil {
		return r.Now()
	}
	return time.Now().UTC()
}

func parseSourceMirrorAutomationSecretRef(raw string, expectedOwner string, expectedRepo string) (string, error) {
	ref := strings.TrimSpace(raw)
	if ref == "" {
		return "", invalidArgument("source mirror credential_ref is required")
	}
	expectedOwner = strings.TrimSpace(expectedOwner)
	expectedRepo = strings.TrimSpace(expectedRepo)
	if expectedOwner == "" || expectedRepo == "" {
		return "", invalidArgument("source mirror credential_ref target repository scope is required")
	}
	const automationSecretPrefix = "automation-secret://"
	if strings.HasPrefix(ref, automationSecretPrefix) {
		parts := splitSourceMirrorCredentialRef(strings.TrimPrefix(ref, automationSecretPrefix))
		switch len(parts) {
		case 1:
			return validateSourceMirrorAutomationSecretName(parts[0])
		case 3:
			if parts[0] != expectedOwner || parts[1] != expectedRepo {
				return "", invalidArgument("source mirror credential_ref must match the target repository scope")
			}
			return validateSourceMirrorAutomationSecretName(parts[2])
		default:
			return "", invalidArgument("source mirror credential_ref must be automation-secret://<name> or automation-secret://<owner>/<repo>/<name>")
		}
	}
	const legacyAutomationPrefix = "secret://automation/"
	if strings.HasPrefix(ref, legacyAutomationPrefix) {
		parts := splitSourceMirrorCredentialRef(strings.TrimPrefix(ref, legacyAutomationPrefix))
		if len(parts) != 3 {
			return "", invalidArgument("source mirror credential_ref must be secret://automation/<owner>/<repo>/<name>")
		}
		if parts[0] != expectedOwner || parts[1] != expectedRepo {
			return "", invalidArgument("source mirror credential_ref must match the target repository scope")
		}
		return validateSourceMirrorAutomationSecretName(parts[2])
	}
	return "", invalidArgument("source mirror credential_ref must reference a automation secret")
}

func splitSourceMirrorCredentialRef(value string) []string {
	rawParts := strings.Split(strings.Trim(value, "/"), "/")
	parts := make([]string, 0, len(rawParts))
	for _, part := range rawParts {
		part = strings.TrimSpace(part)
		if part != "" {
			parts = append(parts, part)
		}
	}
	return parts
}

func validateSourceMirrorAutomationSecretName(name string) (string, error) {
	name = strings.TrimSpace(name)
	if name == "" || len(name) > 128 || !dsl.ValidEnvReferenceName(name) {
		return "", invalidArgument("Automation secret name must match [A-Za-z_][A-Za-z0-9_]* and be at most 128 bytes")
	}
	return name, nil
}
