/**
 * Badge Service
 *
 * HTTP service for serving certification badges with verification endpoints.
 * Designed for deployment on Vercel/Cloudflare edge with caching.
 *
 * @module badge-service
 */
import { generateBadgeSvg } from "../certification/badge.js";
/**
 * In-memory storage for development/testing
 */
export class MemoryCertificationStorage {
    certifications = new Map();
    async getCertification(id) {
        return this.certifications.get(id) || null;
    }
    async storeCertification(record) {
        this.certifications.set(record.id, record);
    }
    async listCertifications() {
        return Array.from(this.certifications.values());
    }
}
// ============================================================================
// Badge Generation
// ============================================================================
/**
 * Generate an expired/invalid badge SVG
 */
export function generateExpiredBadgeSvg() {
    const width = 140;
    const height = 20;
    return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" role="img" aria-label="certification: expired">
  <title>certification: expired</title>
  <linearGradient id="s" x2="0" y2="100%">
    <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
    <stop offset="1" stop-opacity=".1"/>
  </linearGradient>
  <clipPath id="r">
    <rect width="${width}" height="${height}" rx="3" fill="#fff"/>
  </clipPath>
  <g clip-path="url(#r)">
    <rect width="80" height="${height}" fill="#555"/>
    <rect x="80" width="60" height="${height}" fill="#9ca3af"/>
    <rect width="${width}" height="${height}" fill="url(#s)"/>
  </g>
  <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
    <text aria-hidden="true" x="40" y="15" fill="#010101" fill-opacity=".3">certification</text>
    <text x="40" y="14">certification</text>
    <text aria-hidden="true" x="110" y="15" fill="#010101" fill-opacity=".3">expired</text>
    <text x="110" y="14">expired</text>
  </g>
</svg>`;
}
/**
 * Generate a not-found badge SVG
 */
export function generateNotFoundBadgeSvg() {
    const width = 150;
    const height = 20;
    return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" role="img" aria-label="certification: not found">
  <title>certification: not found</title>
  <linearGradient id="s" x2="0" y2="100%">
    <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
    <stop offset="1" stop-opacity=".1"/>
  </linearGradient>
  <clipPath id="r">
    <rect width="${width}" height="${height}" rx="3" fill="#fff"/>
  </clipPath>
  <g clip-path="url(#r)">
    <rect width="80" height="${height}" fill="#555"/>
    <rect x="80" width="70" height="${height}" fill="#dc2626"/>
    <rect width="${width}" height="${height}" fill="url(#s)"/>
  </g>
  <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
    <text aria-hidden="true" x="40" y="15" fill="#010101" fill-opacity=".3">certification</text>
    <text x="40" y="14">certification</text>
    <text aria-hidden="true" x="115" y="15" fill="#010101" fill-opacity=".3">not found</text>
    <text x="115" y="14">not found</text>
  </g>
</svg>`;
}
// ============================================================================
// Validation
// ============================================================================
/**
 * Check if a certification is expired
 */
export function isExpired(cert, graceMinutes = 0) {
    const expiresAt = new Date(cert.expiresAt);
    const now = new Date();
    const graceMs = graceMinutes * 60 * 1000;
    return now.getTime() > expiresAt.getTime() + graceMs;
}
/**
 * Add days to a date
 */
export function addDays(dateStr, days) {
    const date = new Date(dateStr);
    date.setDate(date.getDate() + days);
    return date.toISOString();
}
// ============================================================================
// Badge Embed Code Generation
// ============================================================================
/**
 * Generate embed code for a certification badge
 */
export function generateBadgeEmbedCode(certificationId, baseUrl = "https://badges.vaspera.dev") {
    const badgeUrl = `${baseUrl}/v/${certificationId}.svg`;
    const verifyUrl = `${baseUrl}/v/${certificationId}/verify`;
    return {
        markdown: `[![Vaspera Certified](${badgeUrl})](${verifyUrl})`,
        html: `<a href="${verifyUrl}"><img src="${badgeUrl}" alt="Vaspera Certified" /></a>`,
        url: badgeUrl,
        verifyUrl,
    };
}
// ============================================================================
// Request Handlers
// ============================================================================
/**
 * Handle badge SVG request
 */
export async function handleBadgeRequest(certId, storage) {
    const cert = await storage.getCertification(certId);
    const headers = {
        "Content-Type": "image/svg+xml",
        "Cache-Control": "public, max-age=3600",
    };
    if (!cert) {
        return {
            svg: generateNotFoundBadgeSvg(),
            status: 200, // Return 200 so badge still displays
            headers,
        };
    }
    if (isExpired(cert)) {
        return {
            svg: generateExpiredBadgeSvg(),
            status: 200,
            headers,
        };
    }
    return {
        svg: generateBadgeSvg(cert.level, cert.score),
        status: 200,
        headers,
    };
}
/**
 * Handle verification request
 */
export async function handleVerifyRequest(certId, storage) {
    const cert = await storage.getCertification(certId);
    if (!cert) {
        return {
            json: {
                valid: false,
                certificationId: certId,
                error: "Certification not found",
            },
            status: 404,
        };
    }
    const expired = isExpired(cert);
    return {
        json: {
            valid: !expired,
            certificationId: certId,
            level: cert.level,
            score: cert.score,
            issuedAt: cert.issuedAt,
            expiresAt: cert.expiresAt,
            expired,
            sigstoreBundle: cert.sigstoreBundle,
            rekorLogIndex: cert.rekorLogIndex,
            verifyCommand: cert.signatureUrl && cert.certUrl
                ? `cosign verify-blob --signature ${cert.signatureUrl} --certificate ${cert.certUrl}`
                : undefined,
        },
        status: expired ? 410 : 200,
    };
}
/**
 * Handle embed code request
 */
export async function handleEmbedRequest(certId, storage, baseUrl = "https://badges.vaspera.dev") {
    const cert = await storage.getCertification(certId);
    if (!cert) {
        return {
            json: { error: "Certification not found" },
            status: 404,
        };
    }
    return {
        json: generateBadgeEmbedCode(certId, baseUrl),
        status: 200,
    };
}
// ============================================================================
// Exports
// ============================================================================
export { generateBadgeSvg, generateScoreBadgeSvg, } from "../certification/badge.js";
//# sourceMappingURL=index.js.map