// Package runtimepermissionstate owns the production Runtime process gate for
// the Permission State v1 protocol. It binds one startup lineage to a
// machine-readable capability and re-proves durable readiness without exposing
// SQL, catalog, or build-info mechanics to process composition.
package runtimepermissionstate

import (
	"context"
	"database/sql"
	"encoding/hex"
	"errors"
	"fmt"
	"regexp"
	"runtime/debug"
	"sort"
	"strings"
	"time"

	"github.com/nexuscrm/backend/internal/runtimeconfig"
	"github.com/nexuscrm/kernel/pkg/schemacatalog"
	"github.com/nexuscrm/persistence"
)

const (
	ProtocolV1             = runtimeconfig.PermissionStateProtocolV1
	RoleReader             = Role("reader")
	RoleReaderWriter       = Role("reader-writer")
	authorizationComposeV1 = "nexuscrm.permission-state-v1-authorization.v1"

	// Readiness performs a fresh durable-authority read. Two seconds was too
	// short for supported remote TiDB deployments and caused healthy processes
	// to flap out of service on ordinary network latency. Keep this bounded and
	// fail closed, but leave enough time for the authority read to complete.
	defaultReadinessTimeout = 30 * time.Second
)

var exactSourceRevisionPattern = regexp.MustCompile(`^[0-9a-f]{40}$`)

type Role string

func (role Role) valid() bool {
	return role == RoleReader || role == RoleReaderWriter
}

type configurationMode uint8

const (
	configurationUnsupported configurationMode = iota
	configurationLegacyDark
	configurationPermissionStateV1
)

func modeForConfiguration(schema string) configurationMode {
	switch schema {
	case runtimeconfig.ServingConfigSchemaV2,
		runtimeconfig.WorkerConfigSchemaV1,
		runtimeconfig.WorkerConfigSchemaV2,
		runtimeconfig.ServerPermissionStateLegacySchema:
		return configurationLegacyDark
	case runtimeconfig.ServingConfigSchemaV3,
		runtimeconfig.ServingConfigSchemaV4,
		runtimeconfig.WorkerConfigSchemaV3,
		runtimeconfig.ServerPermissionStateConfigSchemaV1:
		return configurationPermissionStateV1
	default:
		return configurationUnsupported
	}
}

type Config struct {
	SourceRevision          string
	ConfigurationSchema     string
	PermissionStateProtocol string
	Role                    Role
	ReadinessTimeout        time.Duration
}

type stateSource interface {
	CheckReady(context.Context) (persistence.PermissionState, error)
	LoadCurrent(context.Context) (persistence.PermissionState, error)
}

// Gate is one startup-bound Permission State capability and readiness proof.
// Capability facts are immutable. Readiness is a fresh strong read and rejects
// a replacement lineage; legacy configuration additionally rejects enforced
// mode.
type Gate struct {
	source           stateSource
	initial          persistence.PermissionState
	allowEnforced    bool
	readinessTimeout time.Duration
	capabilities     []string
}

// NewGate loads the current durable state, resolves the permission-state
// capability identity, and returns the process gate. Legacy serving/worker/server schemas
// remain source-compatible only while durable mode is dark. Current schemas
// require the exact v1 protocol. Runtime API and worker identities also require
// an exact source revision; the single-process server is identified directly
// by the version of its running binary.
func NewGate(ctx context.Context, db *sql.DB, config Config) (*Gate, error) {
	return newGateWithBuildInfo(ctx, db, config, nil)
}

func newGateWithBuildInfo(
	ctx context.Context,
	db *sql.DB,
	config Config,
	info *debug.BuildInfo,
) (*Gate, error) {
	if db == nil {
		return nil, errors.New("runtime permission state: database is required")
	}
	repository, err := persistence.NewPermissionStateRepository(db)
	if err != nil {
		return nil, err
	}
	switch modeForConfiguration(config.ConfigurationSchema) {
	case configurationLegacyDark:
		return newGate(ctx, repository, config, capabilityFacts{})
	case configurationUnsupported:
		return nil, errors.New("runtime permission state: configuration schema is unsupported")
	}
	facts, err := loadCapabilityFacts(info)
	if err != nil {
		return nil, err
	}
	return newGate(ctx, repository, config, facts)
}

func newGate(
	ctx context.Context,
	source stateSource,
	config Config,
	facts capabilityFacts,
) (*Gate, error) {
	if ctx == nil {
		return nil, errors.New("runtime permission state: context is required")
	}
	if source == nil {
		return nil, errors.New("runtime permission state: state source is required")
	}
	if !config.Role.valid() {
		return nil, errors.New("runtime permission state: reader/writer role is invalid")
	}
	if strings.TrimSpace(config.SourceRevision) == "" ||
		config.SourceRevision != strings.TrimSpace(config.SourceRevision) {
		return nil, errors.New("runtime permission state: source revision is required")
	}
	allowEnforced := false
	switch modeForConfiguration(config.ConfigurationSchema) {
	case configurationLegacyDark:
		if config.PermissionStateProtocol != "" {
			return nil, errors.New("runtime permission state: legacy config cannot select a protocol")
		}
	case configurationPermissionStateV1:
		if config.PermissionStateProtocol != ProtocolV1 {
			return nil, errors.New("runtime permission state: v1 config requires Permission State v1")
		}
		if config.ConfigurationSchema !=
			runtimeconfig.ServerPermissionStateConfigSchemaV1 &&
			!exactSourceRevisionPattern.MatchString(config.SourceRevision) {
			return nil, errors.New("runtime permission state: v1 config requires an exact source revision")
		}
		if err := facts.validate(); err != nil {
			return nil, err
		}
		allowEnforced = true
	default:
		return nil, errors.New("runtime permission state: configuration schema is unsupported")
	}
	initial, err := source.CheckReady(ctx)
	if err != nil {
		return nil, fmt.Errorf("runtime permission state: initial readiness: %w", err)
	}
	if err := initial.Validate(); err != nil {
		return nil, errors.New("runtime permission state: initial state is invalid")
	}
	if !allowEnforced && initial.EnforcementMode != persistence.PermissionStateDarkMode {
		return nil, errors.New("runtime permission state: legacy config requires dark mode")
	}
	timeout := config.ReadinessTimeout
	if timeout == 0 {
		timeout = defaultReadinessTimeout
	}
	if timeout < 0 {
		return nil, errors.New("runtime permission state: readiness timeout must be positive")
	}
	gate := &Gate{
		source: source, initial: initial, allowEnforced: allowEnforced,
		readinessTimeout: timeout,
	}
	if allowEnforced {
		gate.capabilities = capabilityStrings(config, initial, facts)
	}
	return gate, nil
}

// Capabilities returns a sorted defensive copy of the v3 capability facts.
// Legacy composition returns an empty set and therefore cannot satisfy an
// external pre-activation capability gate.
func (gate *Gate) Capabilities() []string {
	if gate == nil {
		return nil
	}
	return append([]string(nil), gate.capabilities...)
}

// Ready strongly re-proves the singleton on every probe. Revision and mode may
// advance within a v3 process's startup lineage; lineage replacement always
// removes readiness, and legacy composition never remains ready after
// enforcement.
func (gate *Gate) Ready() bool {
	if gate == nil || gate.source == nil || gate.readinessTimeout <= 0 {
		return false
	}
	ctx, cancel := context.WithTimeout(context.Background(), gate.readinessTimeout)
	defer cancel()
	current, err := gate.source.LoadCurrent(ctx)
	if err != nil {
		return false
	}
	if err := current.Validate(); err != nil {
		return false
	}
	comparison, err := current.Version.Compare(gate.initial.Version)
	if err != nil || comparison < 0 {
		return false
	}
	if gate.initial.EnforcementMode == persistence.PermissionStateEnforcedMode &&
		current.EnforcementMode != persistence.PermissionStateEnforcedMode {
		return false
	}
	return gate.allowEnforced ||
		current.EnforcementMode == persistence.PermissionStateDarkMode
}

type capabilityFacts struct {
	schemaFingerprint    string
	mutationDigest       string
	directWriterDigest   string
	authorizationCompose string
}

func (facts capabilityFacts) validate() error {
	for _, fact := range []struct {
		name  string
		value string
	}{
		{name: "schema fingerprint", value: facts.schemaFingerprint},
		{name: "mutation inventory", value: facts.mutationDigest},
		{name: "direct-writer inventory", value: facts.directWriterDigest},
		{name: "authorization composition", value: facts.authorizationCompose},
	} {
		if strings.TrimSpace(fact.value) == "" ||
			fact.value != strings.TrimSpace(fact.value) ||
			strings.ContainsAny(fact.value, " \t\r\n") {
			return fmt.Errorf("runtime permission state: %s is invalid", fact.name)
		}
	}
	return nil
}

func loadCapabilityFacts(info *debug.BuildInfo) (capabilityFacts, error) {
	// Build-module versions are deliberately not part of Runtime admission.
	// Local workspaces, vendor builds and released binaries can carry identical
	// permission-state semantics with different Go build metadata. The exact L0
	// source revision is already bound by Config.SourceRevision; the facts below
	// bind the capability shape that actually matters at runtime.
	_ = info
	schemaFingerprint, err := persistence.PermissionStateSchemaFingerprint()
	if err != nil {
		return capabilityFacts{}, err
	}
	mutationInventory, err := schemacatalog.ManagedPermissionStateMutationInventory()
	if err != nil {
		return capabilityFacts{}, err
	}
	directWriters, err := schemacatalog.ManagedPermissionStateDirectWriterInventory()
	if err != nil {
		return capabilityFacts{}, err
	}
	return capabilityFacts{
		schemaFingerprint: schemaFingerprint,
		mutationDigest:    mutationInventory.Digest(), directWriterDigest: directWriters.Digest(),
		authorizationCompose: authorizationComposeV1,
	}, nil
}

func capabilityStrings(
	config Config,
	initial persistence.PermissionState,
	facts capabilityFacts,
) []string {
	capabilities := []string{
		ProtocolV1,
		"permission_state.source_revision=" + config.SourceRevision,
		"permission_state.schema=" + facts.schemaFingerprint,
		"permission_state.mutation_inventory=" + facts.mutationDigest,
		"permission_state.direct_writer_inventory=" + facts.directWriterDigest,
		"permission_state.composition=" + facts.authorizationCompose,
		"permission_state.role=" + string(config.Role),
		"permission_state.lineage=" + hex.EncodeToString(initial.Version.Lineage().Bytes()),
		"permission_state.mode_support=dark,enforced",
		"permission_state.config=" + config.ConfigurationSchema,
	}
	sort.Strings(capabilities)
	return capabilities
}
