package extensionflow

import (
	"context"
	"errors"
	"fmt"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

// BuiltinReauthorization is the trusted envelope a provider runtime re-validates
// through the authoritative generic resolver immediately before or after a
// provider mutation. Every field is server-resolved; none comes from Action
// input.
type BuiltinReauthorization struct {
	WorkspaceID                    string
	AgentID                        string
	AgentMemberID                  string
	AuthorizedByMemberID           string
	ServiceInstallationID          string
	ServiceConnectionID            string
	ServiceConnectionGeneration    string
	ServiceInstallationExtensionID string
	ExtensionName                  string
	Action                         string
	DefinitionVersion              int
	DefinitionDigest               string
	CatalogVersion                 int64
	RequiredScopes                 []string
}

// ReauthorizeBuiltin re-runs the full authoritative resolution chain —
// member identity, live catalog, effective extension selection, action
// availability, host policy, and the exact active connection — on one
// transaction snapshot, then compares every identity facet of the resolution
// against the invocation envelope. Any drift (revoked grant, stale catalog,
// re-issued selection, rotated connection, changed authorizer) fails closed.
func (service *Service) ReauthorizeBuiltin(
	ctx context.Context,
	envelope BuiltinReauthorization,
) error {
	if err := service.validate(); err != nil {
		return err
	}
	if err := service.requireRegistry(); err != nil {
		return err
	}
	err := coredb.WithTx(ctx, service.db, func(tx coredb.Tx) error {
		resolved, err := service.resolveInvocationTx(
			ctx,
			tx,
			service.store.InTx(tx),
			InvokeParams{
				WorkspaceID:   envelope.WorkspaceID,
				AgentID:       envelope.AgentID,
				AgentMemberID: envelope.AgentMemberID,
				ExtensionName: envelope.ExtensionName,
				Action:        envelope.Action,
				Input:         []byte(`{}`),
			},
		)
		if err != nil {
			return err
		}
		return compareResolvedIdentity(resolved, envelope)
	})
	if err != nil && !errors.Is(err, storeerr.ErrForbidden) {
		err = storeerr.Forbidden(
			fmt.Sprintf("Builtin reauthorization failed: %v", err),
		)
	}
	return err
}

// compareResolvedIdentity asserts the freshly resolved invocation identity is
// byte-for-byte the envelope the original invocation was authorized under.
func compareResolvedIdentity(
	resolved invocationResolution,
	envelope BuiltinReauthorization,
) error {
	mismatchf := func(format string, args ...any) error {
		return storeerr.Forbidden(fmt.Sprintf(format, args...))
	}
	selection := resolved.selection
	if selection.ID != envelope.ServiceInstallationExtensionID {
		return mismatchf(
			"reauthorized selection %q does not match the invocation envelope %q",
			selection.ID,
			envelope.ServiceInstallationExtensionID,
		)
	}
	if selection.ServiceInstallationID != envelope.ServiceInstallationID {
		return mismatchf(
			"reauthorized installation %q does not match the invocation envelope %q",
			selection.ServiceInstallationID,
			envelope.ServiceInstallationID,
		)
	}
	if resolved.authorizedByMemberID != envelope.AuthorizedByMemberID {
		return mismatchf(
			"reauthorized authorizer %q does not match the invocation envelope %q",
			resolved.authorizedByMemberID,
			envelope.AuthorizedByMemberID,
		)
	}
	if resolved.action.Name != envelope.Action {
		return mismatchf(
			"reauthorized action %q does not match the invocation envelope %q",
			resolved.action.Name,
			envelope.Action,
		)
	}
	if resolved.definition.Definition.Version != envelope.DefinitionVersion ||
		resolved.definition.Definition.Digest != envelope.DefinitionDigest {
		return mismatchf(
			"reauthorized definition %q does not match the invocation envelope",
			resolved.definition.Definition.Digest,
		)
	}
	if resolved.catalogVersion != envelope.CatalogVersion {
		return mismatchf(
			"reauthorized catalog revision %d does not match the invocation envelope %d",
			resolved.catalogVersion,
			envelope.CatalogVersion,
		)
	}
	if resolved.connection.ID != envelope.ServiceConnectionID ||
		resolved.connectionGeneration != envelope.ServiceConnectionGeneration {
		return mismatchf(
			"reauthorized connection %q (generation %q) does not match the invocation envelope",
			resolved.connection.ID,
			resolved.connectionGeneration,
		)
	}
	return nil
}
