package runtimeidentity

import (
	"bytes"
	"context"
	"crypto/ed25519"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"time"
)

const SigningKeyFileSchemaV1 = "nexuscrm.runtime-signing-keys/v1"

type signingKeyFile struct {
	Schema  string                  `json:"schema"`
	Active  signingKeyFileActive    `json:"active"`
	Overlap []signingKeyFileOverlap `json:"overlap"`
}

type signingKeyFileActive struct {
	SigningKeyID     string `json:"signing_key_id"`
	PrivateKeyBase64 string `json:"private_key_base64"`
}

type signingKeyFileOverlap struct {
	SigningKeyID          string `json:"signing_key_id"`
	PublicKeyBase64       string `json:"public_key_base64"`
	SigningStoppedAt      string `json:"signing_stopped_at"`
	VerificationExpiresAt string `json:"verification_expires_at"`
	Compromised           bool   `json:"compromised"`
}

// SnapshotSigningKeyProvider binds issuance to the same immutable key epoch as
// admission. Operators activate a new current/overlap document only through an
// explicit Runtime restart/reconcile; changing a mounted file cannot make the
// issuer sign tokens that the process verifier does not recognize.
func SnapshotSigningKeyProvider(snapshot SigningKeySnapshot) SigningKeyProvider {
	owned := cloneSigningKeySnapshot(snapshot)
	return func(context.Context, time.Time) (SigningKeySnapshot, error) {
		return cloneSigningKeySnapshot(owned), nil
	}
}

func cloneSigningKeySnapshot(snapshot SigningKeySnapshot) SigningKeySnapshot {
	result := SigningKeySnapshot{Active: ActiveSigningKey{
		SigningKeyID: snapshot.Active.SigningKeyID,
		PrivateKey:   append(ed25519.PrivateKey(nil), snapshot.Active.PrivateKey...),
	}}
	result.Overlap = make([]OverlapVerificationKey, len(snapshot.Overlap))
	for i, key := range snapshot.Overlap {
		result.Overlap[i] = key
		result.Overlap[i].PublicKey = append(ed25519.PublicKey(nil), key.PublicKey...)
	}
	return result
}

func LoadSigningKeySnapshot(path string, now time.Time) (SigningKeySnapshot, error) {
	info, err := os.Stat(path)
	if err != nil {
		return SigningKeySnapshot{}, fmt.Errorf("runtime signing keys: stat secret: %w", err)
	}
	if !info.Mode().IsRegular() || info.Size() == 0 || info.Mode().Perm()&0o007 != 0 {
		return SigningKeySnapshot{}, errors.New("runtime signing keys: secret must be a non-empty non-world-accessible regular file")
	}
	raw, err := os.ReadFile(path)
	if err != nil {
		return SigningKeySnapshot{}, fmt.Errorf("runtime signing keys: read secret: %w", err)
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	var document signingKeyFile
	if err := decoder.Decode(&document); err != nil {
		return SigningKeySnapshot{}, fmt.Errorf("runtime signing keys: decode secret: %w", err)
	}
	if err := ensureJSONEOF(decoder); err != nil {
		return SigningKeySnapshot{}, err
	}
	if document.Schema != SigningKeyFileSchemaV1 || document.Active.SigningKeyID == "" {
		return SigningKeySnapshot{}, errors.New("runtime signing keys: schema and active key id are required")
	}
	privateKey, err := base64.StdEncoding.DecodeString(document.Active.PrivateKeyBase64)
	if err != nil || len(privateKey) != ed25519.PrivateKeySize {
		return SigningKeySnapshot{}, errors.New("runtime signing keys: active private key is invalid")
	}
	snapshot := SigningKeySnapshot{Active: ActiveSigningKey{
		SigningKeyID: document.Active.SigningKeyID, PrivateKey: ed25519.PrivateKey(append([]byte(nil), privateKey...)),
	}}
	for _, encoded := range document.Overlap {
		publicKey, decodeErr := base64.StdEncoding.DecodeString(encoded.PublicKeyBase64)
		stoppedAt, stoppedErr := time.Parse(time.RFC3339, encoded.SigningStoppedAt)
		expiresAt, expiresErr := time.Parse(time.RFC3339, encoded.VerificationExpiresAt)
		if encoded.SigningKeyID == "" || decodeErr != nil || len(publicKey) != ed25519.PublicKeySize || stoppedErr != nil || expiresErr != nil {
			return SigningKeySnapshot{}, errors.New("runtime signing keys: overlap key is invalid")
		}
		snapshot.Overlap = append(snapshot.Overlap, OverlapVerificationKey{
			SigningKeyID: encoded.SigningKeyID, PublicKey: ed25519.PublicKey(append([]byte(nil), publicKey...)),
			SigningStoppedAt: stoppedAt, VerificationExpiresAt: expiresAt, Compromised: encoded.Compromised,
		})
	}
	if _, err := snapshot.verificationKeys(now); err != nil {
		return SigningKeySnapshot{}, err
	}
	return snapshot, nil
}

func ensureJSONEOF(decoder *json.Decoder) error {
	var extra any
	if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
		return errors.New("runtime signing keys: secret must contain exactly one JSON document")
	}
	return nil
}
