package runtimeidentity

import (
	"context"
	"encoding/json"
	"errors"
	"os"
	"path/filepath"
	"reflect"
	"testing"
	"time"

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

type p6DDecision struct {
	Status      string `json:"status"`
	Value       int64  `json:"value"`
	Unit        string `json:"unit"`
	Owner       string `json:"owner"`
	Meaning     string `json:"meaning"`
	Enforcement string `json:"enforcement"`
	Gate        string `json:"gate,omitempty"`
}

type p6DSecurityTimingDecisionReport struct {
	Schema         string `json:"schema"`
	Classification string `json:"classification"`
	Revisions      struct {
		NexusCRM       string `json:"nexuscrm"`
		Contract       string `json:"contract"`
		ContractTag    string `json:"contract_tag"`
		APIHost        string `json:"api_host"`
		APIHostTag     string `json:"api_host_tag"`
		ExactMainCIRun int64  `json:"exact_main_ci_run"`
	} `json:"revisions"`
	Observed struct {
		ProtocolMaxDelegationLifetimeSeconds int64 `json:"protocol_max_delegation_lifetime_seconds"`
		ProtocolMaxVerifierSkewSeconds       int64 `json:"protocol_max_verifier_skew_seconds"`
		RuntimeAttemptCapSeconds             int64 `json:"runtime_attempt_cap_seconds"`
		AuthoritativeAdmissionReadsPerCall   int64 `json:"authoritative_admission_reads_per_call"`
	} `json:"observed"`
	Decisions struct {
		DelegationLifetime      p6DDecision `json:"delegation_lifetime"`
		VerifierClockSkew       p6DDecision `json:"verifier_clock_skew"`
		PlannedKeyOverlap       p6DDecision `json:"planned_key_overlap"`
		EmergencyKeyOverlap     p6DDecision `json:"emergency_key_overlap"`
		RevocationTombstoneTail p6DDecision `json:"revocation_tombstone_tail"`
		SecurityLeaseDuration   p6DDecision `json:"security_lease_duration"`
		SecurityLeaseRenewal    p6DDecision `json:"security_lease_renewal"`
		MaxSecurityStaleness    p6DDecision `json:"max_security_staleness"`
		InFlightAuthorization   p6DDecision `json:"in_flight_authorization_window"`
		LeaseBackedStaleServing p6DDecision `json:"lease_backed_stale_serving"`
	} `json:"decisions"`
	ProductionCutoverGates []string `json:"production_cutover_gates"`
}

func TestP6DSecurityTimingDecisionGolden(t *testing.T) {
	report := p6DBuildSecurityTimingDecisionReport()
	wantBytes, err := os.ReadFile(filepath.Join("testdata", "runtime_security_timing_policy_golden.json"))
	if err != nil {
		t.Fatalf("read P6-D golden: %v", err)
	}
	var want p6DSecurityTimingDecisionReport
	if err := json.Unmarshal(wantBytes, &want); err != nil {
		t.Fatalf("decode P6-D golden: %v", err)
	}
	if !reflect.DeepEqual(report, want) {
		got, _ := json.MarshalIndent(report, "", "  ")
		t.Fatalf("P6-D security timing decision changed; review the ADR and golden deliberately:\n%s", got)
	}
}

func TestP6DSecurityTimingPolicyAlgebra(t *testing.T) {
	report := p6DBuildSecurityTimingDecisionReport()
	seconds := func(decision p6DDecision) time.Duration {
		t.Helper()
		if decision.Unit != "seconds" {
			t.Fatalf("decision has unit %q, want seconds: %#v", decision.Unit, decision)
		}
		return time.Duration(decision.Value) * time.Second
	}

	lifetime := seconds(report.Decisions.DelegationLifetime)
	skew := seconds(report.Decisions.VerifierClockSkew)
	if lifetime > delegationv1.MaxLifetime {
		t.Fatalf("production lifetime %s exceeds protocol ceiling %s", lifetime, delegationv1.MaxLifetime)
	}
	if skew > delegationv1.MaxClockSkew {
		t.Fatalf("verifier skew %s exceeds protocol ceiling %s", skew, delegationv1.MaxClockSkew)
	}
	if got, want := seconds(report.Decisions.PlannedKeyOverlap), lifetime+skew; got != want {
		t.Fatalf("planned key overlap = %s, want lifetime + skew = %s", got, want)
	}
	if got := seconds(report.Decisions.EmergencyKeyOverlap); got != 0 {
		t.Fatalf("emergency key overlap = %s, want immediate retirement", got)
	}
	if got := seconds(report.Decisions.RevocationTombstoneTail); got != skew {
		t.Fatalf("revocation tombstone tail = %s, want verifier skew %s", got, skew)
	}
	if got, want := seconds(report.Decisions.SecurityLeaseDuration), 3*seconds(report.Decisions.SecurityLeaseRenewal); got != want {
		t.Fatalf("lease duration = %s, want three renewal intervals = %s", got, want)
	}
	if got := seconds(report.Decisions.MaxSecurityStaleness); got != 0 {
		t.Fatalf("MaxSecurityStaleness = %s, want no intentional stale admission", got)
	}
	if got := seconds(report.Decisions.InFlightAuthorization); got != recordsv1.MaxAttemptDuration {
		t.Fatalf("in-flight authorization window = %s, want existing attempt cap %s", got, recordsv1.MaxAttemptDuration)
	}
	if report.Decisions.LeaseBackedStaleServing.Status != "REJECTED" || report.Decisions.LeaseBackedStaleServing.Value != 0 {
		t.Fatalf("first cutover must reject lease-backed stale serving: %#v", report.Decisions.LeaseBackedStaleServing)
	}
	if report.Observed.AuthoritativeAdmissionReadsPerCall != 1 {
		t.Fatalf("authoritative security reads per call = %d, want one", report.Observed.AuthoritativeAdmissionReadsPerCall)
	}
}

// TestP6DDecisionPinsImmutableEvidence freezes the released evidence used to
// approve the P6-D policy. Current module releases are deliberately not
// allowlisted here: release versions are not security semantics, and a
// hand-maintained version-to-SHA map neither verifies tag resolution nor
// exercises admission behavior. Compatibility remains enforced by the policy
// algebra, golden report, and fail-closed state-machine tests in this package.
func TestP6DDecisionPinsImmutableEvidence(t *testing.T) {
	report := p6DBuildSecurityTimingDecisionReport()
	for name, revision := range map[string]string{
		"NexusCRM": report.Revisions.NexusCRM,
		"contract": report.Revisions.Contract,
		"api-host": report.Revisions.APIHost,
	} {
		if len(revision) != 40 {
			t.Fatalf("%s revision %q is not an exact commit", name, revision)
		}
	}
	if report.Revisions.ContractTag != "v0.1.10" || report.Revisions.APIHostTag != "v0.5.0" || report.Revisions.ExactMainCIRun == 0 {
		t.Fatalf("P6-D lost a released dependency or exact-main CI pin: %#v", report.Revisions)
	}
	for _, decision := range []p6DDecision{
		report.Decisions.DelegationLifetime,
		report.Decisions.VerifierClockSkew,
		report.Decisions.PlannedKeyOverlap,
		report.Decisions.EmergencyKeyOverlap,
		report.Decisions.RevocationTombstoneTail,
		report.Decisions.SecurityLeaseDuration,
		report.Decisions.SecurityLeaseRenewal,
		report.Decisions.MaxSecurityStaleness,
		report.Decisions.InFlightAuthorization,
	} {
		if decision.Status != "APPROVED" || decision.Owner == "" || decision.Enforcement == "" {
			t.Fatalf("approved P6-D decision lacks an owner or enforcement gate: %#v", decision)
		}
	}
}

func TestP6DLeaseTimingBoundsOwnerFailureWithoutCachingAdmission(t *testing.T) {
	report := p6DBuildSecurityTimingDecisionReport()
	lease := time.Duration(report.Decisions.SecurityLeaseDuration.Value) * time.Second
	renewal := time.Duration(report.Decisions.SecurityLeaseRenewal.Value) * time.Second
	base := time.Date(2026, 7, 13, 12, 0, 0, 0, time.UTC)
	now := base
	expiresAt := base.Add(lease)
	var ownerErr error
	state, err := runtimeapi.NewRuntimeSecurityState(context.Background(), runtimeapi.RuntimeSecurityStateConfig{
		Now: func() time.Time { return now },
		Replica: func(context.Context) (runtimeapi.RuntimeReplicaSecurityState, bool, error) {
			if ownerErr != nil {
				return runtimeapi.RuntimeReplicaSecurityState{}, false, ownerErr
			}
			return runtimeapi.RuntimeReplicaSecurityState{
				OrgID: "org-policy", RuntimeAudience: "runtime-policy", SecurityEpoch: 1,
				LeaseSecurityEpoch: 1, LeaseExpiresAt: expiresAt,
			}, true, nil
		},
		Admission: func(context.Context, delegationv1.Claims) (runtimeapi.RuntimeAdmissionSecurityState, error) {
			return runtimeapi.RuntimeAdmissionSecurityState{}, nil
		},
	})
	if err != nil || !state.Ready() {
		t.Fatalf("initial 30-second lease ready=%v err=%v", state.Ready(), err)
	}
	now = base.Add(lease - time.Nanosecond)
	if !state.Ready() {
		t.Fatal("lease became not-ready before its exclusive expiry")
	}
	now = base.Add(lease)
	if state.Ready() {
		t.Fatal("lease remained ready at its exclusive expiry")
	}

	now = base.Add(renewal)
	expiresAt = now.Add(lease)
	if err := state.Refresh(context.Background()); err != nil || !state.Ready() {
		t.Fatalf("explicit renewal ready=%v err=%v", state.Ready(), err)
	}
	ownerErr = errors.New("authority partition")
	if err := state.Refresh(context.Background()); err == nil || !state.Ready() {
		t.Fatalf("failed authoritative refresh ready=%v err=%v, want valid lease retained", state.Ready(), err)
	}
	now = expiresAt
	if state.Ready() {
		t.Fatal("failed refresh extended readiness beyond the authority-issued lease")
	}
}

func p6DBuildSecurityTimingDecisionReport() p6DSecurityTimingDecisionReport {
	approved := func(value int64, owner, meaning, enforcement, gate string) p6DDecision {
		return p6DDecision{Status: "APPROVED", Value: value, Unit: "seconds", Owner: owner, Meaning: meaning, Enforcement: enforcement, Gate: gate}
	}

	var report p6DSecurityTimingDecisionReport
	report.Schema = "p6-d.v1"
	report.Classification = "approved first-cutover security timing policy; no production issuer, lease controller, route, or serving authority is enabled"
	report.Revisions.NexusCRM = "e06339fa10d88ef0f832aef594fc365912e596a5"
	report.Revisions.Contract = "c01105baf6345bad21045530994f8da15e9ff988"
	report.Revisions.ContractTag = "v0.1.10"
	report.Revisions.APIHost = "77c66b28d48b4089682c918a2e82c226a27281eb"
	report.Revisions.APIHostTag = "v0.5.0"
	report.Revisions.ExactMainCIRun = 29225504045
	report.Observed.ProtocolMaxDelegationLifetimeSeconds = int64(delegationv1.MaxLifetime / time.Second)
	report.Observed.ProtocolMaxVerifierSkewSeconds = int64(delegationv1.MaxClockSkew / time.Second)
	report.Observed.RuntimeAttemptCapSeconds = int64(recordsv1.MaxAttemptDuration / time.Second)
	report.Observed.AuthoritativeAdmissionReadsPerCall = 1
	report.Decisions.DelegationLifetime = approved(60, "Runtime Identity issuer", "maximum production lifetime from iat to exp; the 10-minute contract value remains only a hard ceiling", "issuer rejects any requested lifetime above this value", "P7/P5 production issuer assembly and exact/one-over tests")
	report.Decisions.VerifierClockSkew = approved(5, "Runtime Identity key/verifier policy", "whole-second allowance applied only to delegation iat/exp checks", "every production verifier is constructed with this value", "P7/P5 clock-health and exact-boundary tests")
	report.Decisions.PlannedKeyOverlap = approved(65, "Runtime Identity key authority", "old verification key remains overlap from signing cutover through production lifetime plus verifier skew", "two-phase rotation prepublishes and acknowledges the new key before signing cutover, then retires the old key at the overlap boundary", "P5 Runtime Identity key-provider assembly and rotation drill")
	report.Decisions.EmergencyKeyOverlap = approved(0, "Runtime Identity key authority", "a suspected-compromised key is retired immediately even though existing delegations fail", "emergency rotation bypasses planned overlap and advances authoritative key state", "P5 compromised-key drill")
	report.Decisions.RevocationTombstoneTail = approved(5, "Runtime Identity revocation authority", "a delegation tombstone remains authoritative through exp plus configured verifier skew", "revocation expiry is derived from authenticated claims and cannot be shortened by a caller", "P5 production issuer/revocation wiring and exact-boundary test")
	report.Decisions.SecurityLeaseDuration = approved(30, "Runtime Identity/Security lease authority", "maximum issued lease lifetime; it fences readiness and never authorizes cached stale serving", "authority-clock lease issuance rejects longer values", "P7 authority-clock renewal controller and expiry test")
	report.Decisions.SecurityLeaseRenewal = approved(10, "Runtime replica serving-state controller", "target renewal interval; a failed refresh rejects no less than its own attempt and process eligibility remains bounded by the unexpired authority-issued lease", "controller refreshes explicitly, never extends a lease from the replica wall clock, and every request still performs its authoritative admission read", "P7 pause/partition/renewal matrix")
	report.Decisions.MaxSecurityStaleness = approved(0, "Runtime security admission", "maximum intentional cached-authority age for an attempt whose admission begins after a security change commits", "every attempt performs the authoritative security read; a lease never substitutes for it", "P7 atomic serving view and P5 next-attempt revocation test")
	report.Decisions.InFlightAuthorization = approved(2, "Runtime capability", "trusted attempt-context cap for a call admitted before a security change commits; not a client response-delivery SLO", "the existing selected-root attempt deadline bounds Runtime execution while transport and route owners retain their own lifecycle", "P5 revocation-during-in-flight-attempt and route-deadline tests")
	report.Decisions.LeaseBackedStaleServing = p6DDecision{Status: "REJECTED", Value: 0, Unit: "enabled boolean", Owner: "Runtime security admission", Meaning: "the first production slice may not substitute an unexpired lease for the current per-call authoritative security read", Enforcement: "one admission snapshot per attempt before session and Records", Gate: "a future cached snapshot requires a new measured ADR and may not exceed MaxSecurityStaleness"}
	report.ProductionCutoverGates = []string{
		"P7: bind one authoritative security snapshot to one metadata serving view and preserve one read path",
		"P7: issue and renew leases from authority/database time; a replica clock may only shorten the remaining lease and verifier skew never extends it",
		"P5 Runtime Identity: prepublish and acknowledge a new verification key before signing cutover; retain the old key for exactly the planned overlap and retire compromised keys immediately",
		"P5: prove clock health, lease pause/partition recovery, normal and emergency rotation, revocation during an in-flight attempt, and zero stale admission of the next attempt",
	}
	return report
}
