package demo;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.*;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/** HTTPS (configured in application.properties) plus a hand-written ES256 JWT, and a report of which JCA provider serves each algorithm. */
@SpringBootApplication
@RestController
public class FipsDemoApplication {

    // Demo key pair created at start-up. A real service loads its key from a keystore.
    private final KeyPair signingKey = newKey();

    public static void main(String[] args) {
        SpringApplication.run(FipsDemoApplication.class, args);
    }

    private static KeyPair newKey() {
        try {
            KeyPairGenerator g = KeyPairGenerator.getInstance("EC");
            g.initialize(new ECGenParameterSpec("secp256r1"));
            return g.generateKeyPair();
        } catch (GeneralSecurityException e) {
            throw new IllegalStateException(e);
        }
    }

    private static String b64(byte[] b) {
        return Base64.getUrlEncoder().withoutPadding().encodeToString(b);
    }

    @PostMapping("/token")
    public Map<String, String> token(@RequestParam(defaultValue = "alice") String user) throws Exception {
        String header = b64("{\"alg\":\"ES256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8));
        long exp = System.currentTimeMillis() / 1000 + 300;
        String payload = b64(("{\"sub\":\"" + user.replaceAll("[^A-Za-z0-9_.-]", "") + "\",\"exp\":" + exp + "}")
                .getBytes(StandardCharsets.UTF_8));
        // The P1363 format is the raw R||S encoding that JWS requires for ES256.
        Signature s = Signature.getInstance("SHA256withECDSAinP1363Format");
        s.initSign(signingKey.getPrivate());
        s.update((header + "." + payload).getBytes(StandardCharsets.US_ASCII));
        return Map.of("token", header + "." + payload + "." + b64(s.sign()),
                      "signatureProvider", s.getProvider().getName());
    }

    @GetMapping("/whoami")
    public ResponseEntity<Map<String, Object>> whoami(@RequestHeader(value = "Authorization", defaultValue = "") String auth)
            throws Exception {
        if (!auth.startsWith("Bearer ")) return ResponseEntity.status(401).body(Map.of("error", "missing bearer token"));
        String[] p = auth.substring(7).split("\\.");
        if (p.length != 3) return ResponseEntity.status(401).body(Map.of("error", "malformed token"));
        Signature v = Signature.getInstance("SHA256withECDSAinP1363Format");
        v.initVerify(signingKey.getPublic());
        v.update((p[0] + "." + p[1]).getBytes(StandardCharsets.US_ASCII));
        boolean ok = v.verify(Base64.getUrlDecoder().decode(p[2]));
        if (!ok) return ResponseEntity.status(401).body(Map.of("error", "bad signature"));
        String claims = new String(Base64.getUrlDecoder().decode(p[1]), StandardCharsets.UTF_8);
        return ResponseEntity.ok(Map.of("verified", true, "claims", claims, "verifyProvider", v.getProvider().getName()));
    }

    /** Which provider answers for each algorithm this service uses, plus two it should never use in a FIPS posture. */
    @GetMapping("/providers")
    public Map<String, Object> providers() throws Exception {
        Map<String, String> used = new LinkedHashMap<>();
        used.put("MessageDigest SHA-256", MessageDigest.getInstance("SHA-256").getProvider().getName());
        used.put("Signature SHA256withECDSA", Signature.getInstance("SHA256withECDSA").getProvider().getName());
        used.put("Signature SHA256withRSA", Signature.getInstance("SHA256withRSA").getProvider().getName());
        used.put("Mac HmacSHA256", Mac.getInstance("HmacSHA256").getProvider().getName());
        used.put("Cipher AES/GCM/NoPadding", Cipher.getInstance("AES/GCM/NoPadding").getProvider().getName());
        used.put("SecretKeyFactory PBKDF2WithHmacSHA256", SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").getProvider().getName());
        used.put("SecureRandom (default)", new SecureRandom().getProvider().getName() + " " + new SecureRandom().getAlgorithm());
        used.put("MessageDigest MD5 (not FIPS-allowed)", MessageDigest.getInstance("MD5").getProvider().getName());
        used.put("MessageDigest SHA-1 (not for signatures)", MessageDigest.getInstance("SHA-1").getProvider().getName());
        List<String> order = new ArrayList<>();
        for (Provider p : Security.getProviders()) order.add(p.getName());
        return Map.of("providerOrder", order, "serving", used, "javaVersion", System.getProperty("java.version"),
                      "javaVendor", System.getProperty("java.vendor"));
    }
}
