// Package runtimeidentity owns backend composition adapters between Runtime
// security admission and NexusCRM's existing identity authority. It is not in
// the non-serving runtime-api command closure; P6d selects it only when real
// security persistence is assembled.
package runtimeidentity

import (
	"context"
	"errors"
	"time"

	featureauth "github.com/nexuscrm/auth"
	"github.com/nexuscrm/backend/internal/runtimeapi"
	"github.com/nexuscrm/persistence"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/delegationv1"
)

const (
	SecurityLeaseDuration      = 30 * time.Second
	SecurityLeaseRenewalTarget = 10 * time.Second
	// A renewal attempt must finish early enough to leave another full retry
	// window before the current lease expires. It is operational ownership, not
	// a longer serving lease or a relaxation of fail-closed expiry.
	SecurityLeaseRenewalAttemptCap = 5 * time.Second
)

// PublicSessionAuthority reuses AuthService's exact-current-bearer validation.
// The same strong read supplies the complete Runtime authority projection; the
// issuer must not repeat it through SessionAuthority before signing.
func PublicSessionAuthorityFrom(service *featureauth.AuthService) PublicSessionAuthority {
	if service == nil || !service.AuthorizingSessionAdmissionReady() {
		return nil
	}
	return func(ctx context.Context, bearer string) (PublicSession, error) {
		validation, err := service.AdmitRuntimeIdentitySession(ctx, bearer)
		if err != nil {
			return PublicSession{}, err
		}
		if validation == nil || validation.SessionID == "" || validation.AuthEpoch == 0 {
			return PublicSession{}, errors.New("runtime identity: authorizing session result is unavailable")
		}
		return PublicSession{
			SessionID: validation.SessionID,
			State: runtimeapi.RuntimeSessionState{
				User:      validation.User,
				AuthEpoch: validation.AuthEpoch, Subject: validation.Subject,
				PermissionFloor: validation.PermissionFloor,
			},
		}, nil
	}
}

// SessionAuthority exposes only AuthService's bearer-free durable validation
// method. Runtime never receives repositories, a raw bearer, or a mutable
// caller-authored session.
func SessionAuthority(service *featureauth.AuthService) runtimeapi.RuntimeSessionAuthority {
	if service == nil || !service.AuthorizingSessionAdmissionReady() {
		return nil
	}
	return func(ctx context.Context, sessionID, principalID string) (*runtimeapi.RuntimeSessionState, error) {
		state, err := service.AdmitRuntimeAuthorizingSession(ctx, sessionID, principalID)
		if err != nil {
			return nil, err
		}
		return &runtimeapi.RuntimeSessionState{
			User: state.User, AuthEpoch: state.AuthEpoch,
			Subject: state.Subject, PermissionFloor: state.PermissionFloor,
		}, nil
	}
}

// NewSecurityState adapts the one persistence owner into Runtime admission. The
// infrastructure replica ID selects persisted authority; it does not author
// Org, audience, epochs, grants, revocation, or lease truth.
func NewSecurityState(ctx context.Context, repository *persistence.RuntimeSecurityRepository, replicaID string, now func() time.Time) (*runtimeapi.RuntimeSecurityState, error) {
	if repository == nil || replicaID == "" {
		return nil, errors.New("runtime identity: security repository and replica ID are required")
	}
	return runtimeapi.NewRuntimeSecurityState(ctx, runtimeapi.RuntimeSecurityStateConfig{
		Now: now,
		Replica: func(ctx context.Context) (runtimeapi.RuntimeReplicaSecurityState, bool, error) {
			state, found, err := repository.ReadReplicaSecurityContext(ctx, replicaID)
			return runtimeapi.RuntimeReplicaSecurityState{
				OrgID: state.OrgID, RuntimeAudience: state.RuntimeAudience, SecurityEpoch: state.SecurityEpoch,
				LeaseSecurityEpoch: state.LeaseSecurityEpoch, LeaseExpiresAt: state.LeaseExpiresAt,
			}, found, err
		},
		Admission: func(ctx context.Context, claims delegationv1.Claims) (runtimeapi.RuntimeAdmissionSecurityState, error) {
			localStart := nowOrTime(now)
			state, err := repository.ReadAdmissionState(ctx, replicaID, claims.OrgID, claims.AppInstallationID, claims.CallerWorkloadID, claims.DelegationID, localStart)
			return runtimeapi.RuntimeAdmissionSecurityState{
				StateFound: state.StateFound, InstallationFound: state.InstallationFound,
				InstallationActive: state.InstallationActive, Capabilities: append([]string(nil), state.Capabilities...),
				SecurityEpoch: state.SecurityEpoch, LeaseFound: state.LeaseFound,
				LeaseSecurityEpoch: state.LeaseSecurityEpoch, LeaseExpiresAt: conservativeLocalExpiry(localStart, state.AuthorityNow, state.LeaseExpiresAt),
				DelegationRevoked: state.DelegationRevoked,
			}, err
		},
	})
}

// NewRenewingSecurityState is the production replica source. Each explicit
// Refresh renews from the current database-owned epoch and converts the
// authority-clock remaining lifetime onto the replica clock conservatively.
func NewRenewingSecurityState(ctx context.Context, renewalRepository, admissionRepository *persistence.RuntimeSecurityRepository, orgID, replicaID string, now func() time.Time, userAPICallers ...UserAPICallerCapabilities) (*runtimeapi.RuntimeSecurityState, error) {
	if renewalRepository == nil || admissionRepository == nil {
		return nil, errors.New("runtime identity: security repository, org, and replica ID are required")
	}
	return newRenewingSecurityState(ctx, renewalRepository.RenewReplicaLease, admissionRepository.ReadAdmissionState, orgID, replicaID, now, userAPICallers...)
}

type renewReplicaLease func(context.Context, string, string, time.Duration) (persistence.RuntimeReplicaSecurityContext, bool, error)
type readAdmissionSecurityState func(context.Context, string, string, string, string, string, time.Time) (persistence.RuntimeSecurityAdmissionState, error)

func newRenewingSecurityState(ctx context.Context, renew renewReplicaLease, readAdmission readAdmissionSecurityState, orgID, replicaID string, now func() time.Time, userAPICallers ...UserAPICallerCapabilities) (*runtimeapi.RuntimeSecurityState, error) {
	if renew == nil || readAdmission == nil || orgID == "" || replicaID == "" {
		return nil, errors.New("runtime identity: security repository, org, and replica ID are required")
	}
	if now == nil {
		now = time.Now
	}
	return runtimeapi.NewRuntimeSecurityState(ctx, runtimeapi.RuntimeSecurityStateConfig{
		Now: now,
		Replica: func(ctx context.Context) (runtimeapi.RuntimeReplicaSecurityState, bool, error) {
			localStart := now()
			state, found, err := renew(ctx, orgID, replicaID, SecurityLeaseDuration)
			if err != nil || !found {
				return runtimeapi.RuntimeReplicaSecurityState{}, found, err
			}
			remaining := state.LeaseExpiresAt.Sub(state.AuthorityNow)
			if remaining < 0 {
				remaining = 0
			}
			return runtimeapi.RuntimeReplicaSecurityState{
				OrgID: state.OrgID, RuntimeAudience: state.RuntimeAudience, SecurityEpoch: state.SecurityEpoch,
				LeaseSecurityEpoch: state.LeaseSecurityEpoch, LeaseExpiresAt: localStart.Add(remaining),
			}, true, nil
		},
		Admission: func(ctx context.Context, claims delegationv1.Claims) (runtimeapi.RuntimeAdmissionSecurityState, error) {
			localStart := now()
			state, err := readAdmission(ctx, replicaID, claims.OrgID, claims.AppInstallationID, claims.CallerWorkloadID, claims.DelegationID, localStart)
			if err == nil && len(userAPICallers) == 1 && userAPICallers[0] != nil {
				caps, registered := userAPICallers[0](claims.OrgID, claims.AppInstallationID, claims.CallerWorkloadID, state.SecurityEpoch)
				if registered {
					conflict := state.InstallationFound
					state.InstallationFound = state.StateFound && !conflict && len(caps) != 0
					state.InstallationActive = state.InstallationFound
					state.Capabilities = caps
				}
			}
			return runtimeapi.RuntimeAdmissionSecurityState{
				StateFound: state.StateFound, InstallationFound: state.InstallationFound,
				InstallationActive: state.InstallationActive, Capabilities: append([]string(nil), state.Capabilities...),
				SecurityEpoch: state.SecurityEpoch, LeaseFound: state.LeaseFound,
				LeaseSecurityEpoch: state.LeaseSecurityEpoch, LeaseExpiresAt: conservativeLocalExpiry(localStart, state.AuthorityNow, state.LeaseExpiresAt),
				DelegationRevoked: state.DelegationRevoked,
			}, err
		},
	})
}

func IssuanceSecuritySourceFrom(repository *persistence.RuntimeSecurityRepository, replicaID string, now func() time.Time, userAPICallers ...UserAPICallerCapabilities) IssuanceSecuritySource {
	if repository == nil || replicaID == "" {
		return nil
	}
	if now == nil {
		now = time.Now
	}
	return func(ctx context.Context, appInstallationID, callerWorkloadID string) (IssuanceSecurityState, error) {
		localStart := now()
		state, err := repository.ReadIssuanceState(ctx, replicaID, appInstallationID, callerWorkloadID)
		if err == nil && len(userAPICallers) == 1 && userAPICallers[0] != nil {
			caps, registered := userAPICallers[0](state.OrgID, appInstallationID, callerWorkloadID, state.SecurityEpoch)
			if registered {
				conflict := state.BindingFound
				state.BindingFound = state.StateFound && !conflict && len(caps) != 0
				state.BindingActive = state.BindingFound
				state.Capabilities = caps
			}
		}
		return IssuanceSecurityState{
			StateFound: state.StateFound, BindingFound: state.BindingFound, BindingActive: state.BindingActive,
			OrgID: state.OrgID, RuntimeAudience: state.RuntimeAudience,
			Capabilities: append([]string(nil), state.Capabilities...), SecurityEpoch: state.SecurityEpoch,
			LeaseFound: state.LeaseFound, LeaseSecurityEpoch: state.LeaseSecurityEpoch,
			LeaseExpiresAt: conservativeLocalExpiry(localStart, state.AuthorityNow, state.LeaseExpiresAt),
		}, err
	}
}

func conservativeLocalExpiry(localStart, authorityNow, authorityExpiry time.Time) time.Time {
	if localStart.IsZero() || authorityNow.IsZero() || authorityExpiry.IsZero() {
		return time.Time{}
	}
	remaining := authorityExpiry.Sub(authorityNow)
	if remaining < 0 {
		remaining = 0
	}
	return localStart.Add(remaining)
}

func nowOrTime(now func() time.Time) time.Time {
	if now == nil {
		return time.Now()
	}
	return now()
}

// UserAPICallerCapabilities selects an explicit user-only client class against current installed API authority.
type UserAPICallerCapabilities func(org, installation, caller string, epoch uint64) ([]string, bool)
