package ags

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"strings"

	agsauth "github.com/pingcap/agent-git-service/auth"
	agsserver "github.com/pingcap/agent-git-service/server"

	"github.com/tidbcloud/AgentTeam/internal/agsbridge"
	"github.com/tidbcloud/AgentTeam/internal/identity/trusted"
	"github.com/tidbcloud/AgentTeam/internal/service/repotokens"
)

const routeWorkspaceHeader = "X-AgentTeam-Route-Workspace-Id"

type AgentTeamAuthenticator struct {
	Options trusted.Options
}

// RepositoryTokenAuthenticator recognizes MeshX-issued repository credentials
// before delegating ordinary AgentTeam bearer authentication.
type RepositoryTokenAuthenticator struct {
	AgentTeam            AgentTeamAuthenticator
	Tokens          *repotokens.Service
	WorkspaceAccess WorkspaceAccessVerifier
}

type WorkspaceAccessVerifier interface {
	HasWorkspaceAccess(ctx context.Context, workspaceID, principalID string) (bool, error)
}

func (a RepositoryTokenAuthenticator) Authenticate(r *http.Request) (agsauth.Identity, bool, error) {
	plaintext := agsserver.RepositoryCredential(r)
	if !strings.HasPrefix(plaintext, "ags_rat_") {
		return a.AgentTeam.Authenticate(r)
	}
	if a.Tokens == nil {
		return agsauth.Identity{}, true, errors.New("repository credential authority is not configured")
	}
	token, failure, err := a.Tokens.ValidateCredential(r.Context(), plaintext)
	if err != nil {
		return agsauth.Identity{}, true, err
	}
	if failure != repotokens.ValidationFailureNone {
		return agsauth.Identity{}, true, fmt.Errorf("repository credential rejected: %s", failure)
	}
	if routedWorkspaceID := strings.TrimSpace(r.Header.Get(routeWorkspaceHeader)); routedWorkspaceID != "" && routedWorkspaceID != strings.TrimSpace(token.WorkspaceID) {
		return agsauth.Identity{}, true, errors.New("repository credential workspace does not match route owner")
	}
	repository, capability, ok := agsserver.RepositoryAuthorizationTarget(r)
	if !ok {
		return agsauth.Identity{}, true, errors.New("repository credential is not valid for this route")
	}
	if err := a.Tokens.AuthorizeRepository(token, repository, capability); err != nil {
		return agsauth.Identity{}, true, err
	}
	if token.Type == repotokens.TokenTypeHuman {
		if a.WorkspaceAccess == nil {
			return agsauth.Identity{}, true, errors.New("repository credential owner workspace access cannot be verified")
		}
		active, err := a.WorkspaceAccess.HasWorkspaceAccess(r.Context(), token.WorkspaceID, token.OwnerPrincipalID)
		if err != nil {
			return agsauth.Identity{}, true, fmt.Errorf("verify repository credential owner workspace access: %w", err)
		}
		if !active {
			return agsauth.Identity{}, true, errors.New("repository credential owner no longer has workspace access")
		}
	}
	a.Tokens.Touch(r.Context(), token)
	permission := "read"
	if capability == "repo:write" {
		permission = "write"
	}
	identity := agsbridge.FromAgentTeam(trusted.Identity{
		Subject: token.OwnerPrincipalID, PreferredUsername: token.OwnerPrincipalID, Name: token.DisplayName,
	})
	if token.Type != repotokens.TokenTypeHuman {
		identity.Provider = "meshx_repository_token"
		identity.Subject = token.ID
		identity.Login = agsbridge.LoginForAgentTeam(trusted.Identity{PreferredUsername: "rat-" + token.ID})
	}
	identity.RepositoryGrant = agsauth.RepositoryGrant{
		FullName: repository, Permission: permission,
		RequireCurrentAccess: token.Type == repotokens.TokenTypeHuman,
	}
	return identity, true, nil
}

func (a AgentTeamAuthenticator) Authenticate(r *http.Request) (agsauth.Identity, bool, error) {
	header := strings.TrimSpace(a.Options.Header)
	if header == "" {
		header = "Authorization"
	}
	raw := strings.TrimSpace(r.Header.Get(header))
	if raw == "" {
		return agsauth.Identity{}, false, nil
	}
	if strings.EqualFold(header, "Authorization") && !strings.HasPrefix(strings.ToLower(raw), "bearer ") {
		return agsauth.Identity{}, false, nil
	}
	identity, _, err := trusted.IdentityFromRequest(r, a.Options)
	if err != nil {
		return agsauth.Identity{}, false, err
	}
	agsIdentity := agsbridge.FromAgentTeam(identity)
	agsIdentity.SiteAdmin = agsbridge.SiteAdminFromContext(r.Context())
	return agsIdentity, true, nil
}
