package auth

import (
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"encoding/hex"
)

const sessionControlProofEntropyBytes = 32

// newSessionControlProof creates the independently held browser credential and
// the only representation persisted in _System_Session. The raw proof must
// remain outside bearer tokens and durable storage.
func newSessionControlProof() (string, string, error) {
	secret := make([]byte, sessionControlProofEntropyBytes)
	if _, err := rand.Read(secret); err != nil {
		return "", "", err
	}
	proof := base64.RawURLEncoding.EncodeToString(secret)
	return proof, hashSessionControlProof(proof), nil
}

func hashSessionControlProof(proof string) string {
	digest := sha256.Sum256([]byte(proof))
	return hex.EncodeToString(digest[:])
}

// matchingSessionControlProofHash validates the canonical 32-byte proof shape
// and compares its SHA-256 verifier in constant time. It returns the computed
// verifier so persistence compare-and-swap operations never receive the raw
// browser credential.
func matchingSessionControlProofHash(proof string, expected *string) (string, bool) {
	computed, ok := presentedSessionControlProofHash(proof)
	if !ok || expected == nil {
		return "", false
	}
	if !sessionControlProofHashesEqual(computed, expected) {
		return "", false
	}
	return computed, true
}

func presentedSessionControlProofHash(proof string) (string, bool) {
	if proof == "" {
		return "", false
	}
	decoded, err := base64.RawURLEncoding.DecodeString(proof)
	if err != nil || len(decoded) != sessionControlProofEntropyBytes || base64.RawURLEncoding.EncodeToString(decoded) != proof {
		return "", false
	}
	return hashSessionControlProof(proof), true
}

func sessionControlProofHashesEqual(computed string, expected *string) bool {
	if expected == nil {
		return false
	}
	computedBytes, computedErr := hex.DecodeString(computed)
	expectedBytes, expectedErr := hex.DecodeString(*expected)
	if computedErr != nil || expectedErr != nil || len(computedBytes) != sha256.Size || len(expectedBytes) != sha256.Size {
		return false
	}
	return subtle.ConstantTimeCompare(computedBytes, expectedBytes) == 1
}
