package automation

import (
	"context"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/identity/trusted"
	automationstore "github.com/tidbcloud/AgentTeam/internal/store/automation"
)

const (
	automationPermissionAdmin        = "automation:*"
	automationPermissionRepoRead     = "automation:repo:read"
	automationPermissionRepoWrite    = "automation:repo:write"
	automationPermissionRunRead      = "automation:run:read"
	automationPermissionRunStart     = "automation:run:start"
	automationPermissionRunCancel    = "automation:run:cancel"
	automationPermissionEventer      = "automation:eventer"
	automationPermissionWaitResume   = "automation:wait:resume"
	automationPermissionRunnerManage = "automation:runner:manage"
	automationPermissionSecretRead   = "automation:secret:read"
	automationPermissionSecretManage = "automation:secret:manage"
	automationPermissionSecretUse    = "automation:secret:use"
	automationPermissionService      = "automation:service"
)

// AutomationAuthorizer is the automation control-plane authorization boundary.
//
// Implementations must keep checks bounded by the concrete workspace, repo, run,
// wait, or runner carried by the request. Runner execution APIs do not use this
// human/service-actor boundary; they are authenticated by runner session tokens.
type AutomationAuthorizer interface {
	CanReadAutomationRepo(context.Context, AutomationAuthorizationRequest) error
	CanWriteAutomationRepo(context.Context, AutomationAuthorizationRequest) error
	CanStartAutomationRun(context.Context, AutomationAuthorizationRequest) error
	CanReceiveAutomationEvent(context.Context, AutomationAuthorizationRequest) error
	CanReadRun(context.Context, AutomationAuthorizationRequest) error
	CanCancelRun(context.Context, AutomationAuthorizationRequest) error
	CanResumeWait(context.Context, AutomationAuthorizationRequest) error
	CanManageRunner(context.Context, AutomationAuthorizationRequest) error
	CanReadAutomationSecret(context.Context, AutomationAuthorizationRequest) error
	CanManageAutomationSecret(context.Context, AutomationAuthorizationRequest) error
	CanUseAutomationSecret(context.Context, AutomationAuthorizationRequest) error
}

// AutomationAuthorizationRequest describes one automation authorization decision.
type AutomationAuthorizationRequest struct {
	Actor           AutomationActor
	WorkspaceID     string
	RepositoryOwner string
	RepositoryName  string
	Run             automationstore.AutomationRun
	Wait            automationstore.AutomationWait
	RunnerID        string
	JobRunID        string
	LogStream       string
	LogSequence     int64
	ArtifactID      string
}

// AutomationActor describes the authenticated caller presented to the authorizer.
type AutomationActor struct {
	Subject       string
	Groups        []string
	Tenant        string
	Claims        map[string]any
	Authenticated bool
}

// AllowAutomationAuthorizer is intended for service tests and explicit embedding
// points that provide an upstream authorization layer.
type AllowAutomationAuthorizer struct{}

func (AllowAutomationAuthorizer) CanReadAutomationRepo(context.Context, AutomationAuthorizationRequest) error {
	return nil
}
func (AllowAutomationAuthorizer) CanWriteAutomationRepo(context.Context, AutomationAuthorizationRequest) error {
	return nil
}
func (AllowAutomationAuthorizer) CanStartAutomationRun(context.Context, AutomationAuthorizationRequest) error {
	return nil
}
func (AllowAutomationAuthorizer) CanReceiveAutomationEvent(context.Context, AutomationAuthorizationRequest) error {
	return nil
}
func (AllowAutomationAuthorizer) CanReadRun(context.Context, AutomationAuthorizationRequest) error {
	return nil
}
func (AllowAutomationAuthorizer) CanCancelRun(context.Context, AutomationAuthorizationRequest) error {
	return nil
}
func (AllowAutomationAuthorizer) CanResumeWait(context.Context, AutomationAuthorizationRequest) error {
	return nil
}
func (AllowAutomationAuthorizer) CanManageRunner(context.Context, AutomationAuthorizationRequest) error {
	return nil
}
func (AllowAutomationAuthorizer) CanReadAutomationSecret(context.Context, AutomationAuthorizationRequest) error {
	return nil
}
func (AllowAutomationAuthorizer) CanManageAutomationSecret(context.Context, AutomationAuthorizationRequest) error {
	return nil
}
func (AllowAutomationAuthorizer) CanUseAutomationSecret(context.Context, AutomationAuthorizationRequest) error {
	return nil
}

// DenyAutomationAuthorizer is the fail-closed default when MeshX has no explicit
// trusted automation authorization source.
type DenyAutomationAuthorizer struct{}

var errAutomationAuthorizerNotConfigured = forbidden("Automation authorizer is not configured")

func (DenyAutomationAuthorizer) CanReadAutomationRepo(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}
func (DenyAutomationAuthorizer) CanWriteAutomationRepo(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}
func (DenyAutomationAuthorizer) CanStartAutomationRun(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}
func (DenyAutomationAuthorizer) CanReceiveAutomationEvent(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}
func (DenyAutomationAuthorizer) CanReadRun(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}
func (DenyAutomationAuthorizer) CanCancelRun(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}
func (DenyAutomationAuthorizer) CanResumeWait(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}
func (DenyAutomationAuthorizer) CanManageRunner(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}
func (DenyAutomationAuthorizer) CanReadAutomationSecret(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}
func (DenyAutomationAuthorizer) CanManageAutomationSecret(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}
func (DenyAutomationAuthorizer) CanUseAutomationSecret(context.Context, AutomationAuthorizationRequest) error {
	return errAutomationAuthorizerNotConfigured
}

// ClaimAutomationAuthorizer enforces automation access from AgentTeam-verified JWT
// claims. Only use it when an upstream component has already verified the JWT
// signature and issuer.
type ClaimAutomationAuthorizer struct{}

func (a ClaimAutomationAuthorizer) CanReadAutomationRepo(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionRepoRead, false)
}

func (a ClaimAutomationAuthorizer) CanWriteAutomationRepo(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionRepoWrite, false)
}

func (a ClaimAutomationAuthorizer) CanStartAutomationRun(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionRunStart, false)
}

func (a ClaimAutomationAuthorizer) CanReceiveAutomationEvent(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionEventer, true)
}

func (a ClaimAutomationAuthorizer) CanReadRun(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionRunRead, false)
}

func (a ClaimAutomationAuthorizer) CanCancelRun(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionRunCancel, false)
}

func (a ClaimAutomationAuthorizer) CanResumeWait(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionWaitResume, true)
}

func (a ClaimAutomationAuthorizer) CanManageRunner(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionRunnerManage, false)
}

func (a ClaimAutomationAuthorizer) CanReadAutomationSecret(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionSecretRead, false)
}

func (a ClaimAutomationAuthorizer) CanManageAutomationSecret(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionSecretManage, false)
}

func (a ClaimAutomationAuthorizer) CanUseAutomationSecret(_ context.Context, request AutomationAuthorizationRequest) error {
	return a.authorize(request, automationPermissionSecretUse, false)
}

func (ClaimAutomationAuthorizer) authorize(request AutomationAuthorizationRequest, permission string, serviceActorRequired bool) error {
	actor := request.Actor
	if strings.TrimSpace(actor.Subject) == "" {
		return unauthorized("Missing authenticated identity")
	}
	if !actor.Authenticated {
		return forbidden("Automation authorization identity is required")
	}
	if !automationClaimIncludes(actor.Claims, strings.TrimSpace(request.WorkspaceID), automationWorkspaceClaimKeys()...) {
		return forbidden("Automation workspace access is required")
	}
	if serviceActorRequired && !automationActorHasServiceCapability(actor) {
		return forbidden("Automation service actor capability is required")
	}
	if !automationActorHasPermission(actor, permission) {
		return forbidden("Automation permission required: " + permission)
	}
	return nil
}

func automationAuthorizationActor(ctx context.Context, subject string) AutomationActor {
	identity, ok := trusted.IdentityFromContext(ctx)
	if !ok {
		return AutomationActor{Subject: strings.TrimSpace(subject)}
	}
	return AutomationActor{
		Subject:       strings.TrimSpace(identity.Subject),
		Groups:        identity.Groups,
		Tenant:        identity.Tenant,
		Claims:        identity.RawClaims,
		Authenticated: true,
	}
}

func automationActorHasPermission(actor AutomationActor, permission string) bool {
	permissions := automationClaimStrings(actor.Claims, "automation_permissions", "automationPermissions", "meshx_automation_permissions", "permissions", "scope")
	return stringSetContains(permissions, automationPermissionAdmin) || stringSetContains(permissions, permission)
}

func automationActorHasAdminPermission(actor AutomationActor) bool {
	return stringSetContains(automationClaimStrings(actor.Claims, "automation_permissions", "automationPermissions", "meshx_automation_permissions", "permissions", "scope"), automationPermissionAdmin)
}

func automationActorHasServiceCapability(actor AutomationActor) bool {
	permissions := automationClaimStrings(actor.Claims, "automation_permissions", "automationPermissions", "meshx_automation_permissions", "permissions", "scope")
	if stringSetContains(permissions, automationPermissionService) {
		return true
	}
	for _, key := range []string{"service_actor", "serviceActor", "meshx_service_actor"} {
		if value, ok := actor.Claims[key].(bool); ok && value {
			return true
		}
	}
	for _, key := range []string{"actor_kind", "actorKind", "principal_type", "principalType"} {
		for _, value := range automationClaimStrings(actor.Claims, key) {
			if value == "service" {
				return true
			}
		}
	}
	return false
}

func automationWorkspaceClaimKeys() []string {
	return []string{"workspace_id", "workspaceId", "workspace_ids", "workspaceIds", "workspaces", "meshx_workspaces", "meshx_workspace_ids"}
}

func automationClaimIncludes(claims map[string]any, target string, keys ...string) bool {
	target = strings.TrimSpace(target)
	if target == "" {
		return false
	}
	values := automationClaimStrings(claims, keys...)
	return stringSetContains(values, target) || stringSetContains(values, "*") || stringSetContains(values, "all")
}

func automationClaimStrings(claims map[string]any, keys ...string) []string {
	if len(claims) == 0 {
		return nil
	}
	var out []string
	for _, key := range keys {
		raw, ok := claims[key]
		if !ok {
			continue
		}
		switch values := raw.(type) {
		case string:
			out = appendSplitClaim(out, values)
		case []string:
			for _, value := range values {
				out = appendSplitClaim(out, value)
			}
		case []any:
			for _, value := range values {
				if s, ok := value.(string); ok {
					out = appendSplitClaim(out, s)
				}
			}
		}
	}
	return out
}

func appendSplitClaim(out []string, raw string) []string {
	for _, value := range strings.FieldsFunc(raw, func(r rune) bool { return r == ',' || r == ' ' || r == '\n' || r == '\t' }) {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			out = append(out, trimmed)
		}
	}
	return out
}

func stringSetContains(values []string, target string) bool {
	for _, value := range values {
		if strings.EqualFold(strings.TrimSpace(value), target) {
			return true
		}
	}
	return false
}
