import type { Request, Response, NextFunction, RequestHandler } from 'express';
import { enrichAgentCard } from './enrich';
import type { AgentCard, SupportedChain, EnrichedAgentCard } from './types';

export interface VaultfireMiddlewareOptions {
  /** The base A2A Agent Card to enrich */
  agentCard: AgentCard;
  /** The blockchain network where the agent is registered */
  chain: SupportedChain;
  /** The agent's on-chain address (0x...) */
  agentAddress: string;
  /**
   * Cache TTL in milliseconds (default: 5 minutes).
   * Set to 0 to disable caching.
   */
  cacheTtl?: number;
  /**
   * Called when enrichment fails. Defaults to serving the plain agent card.
   * Return null to pass through to the next middleware.
   */
  onError?: (err: Error) => EnrichedAgentCard | null;
}

interface CacheEntry {
  card: EnrichedAgentCard;
  expiresAt: number;
}

/**
 * Express middleware that serves an enriched Agent Card at /.well-known/agent.json.
 *
 * The card is cached to avoid repeated RPC calls. Cache TTL defaults to 5 minutes.
 *
 * @example
 * ```typescript
 * app.get('/.well-known/agent.json', vaultfireAgentCardMiddleware({
 *   agentCard: myCard,
 *   chain: 'base',
 *   agentAddress: '0x...',
 * }));
 * ```
 */
export function vaultfireAgentCardMiddleware(
  options: VaultfireMiddlewareOptions
): RequestHandler {
  const { agentCard, chain, agentAddress, cacheTtl = 5 * 60 * 1000, onError } = options;

  let cache: CacheEntry | null = null;
  let inflightPromise: Promise<EnrichedAgentCard> | null = null;

  return async (_req: Request, res: Response, next: NextFunction): Promise<void> => {
    // Serve from cache if valid
    if (cache && Date.now() < cache.expiresAt) {
      res.setHeader('Content-Type', 'application/json');
      res.setHeader('Cache-Control', `public, max-age=${Math.floor(cacheTtl / 1000)}`);
      res.json(cache.card);
      return;
    }

    // Deduplicate concurrent enrichment requests
    if (!inflightPromise) {
      inflightPromise = enrichAgentCard({ agentCard, chain, agentAddress }).finally(() => {
        inflightPromise = null;
      });
    }

    try {
      const enriched = await inflightPromise;

      if (cacheTtl > 0) {
        cache = { card: enriched, expiresAt: Date.now() + cacheTtl };
      }

      res.setHeader('Content-Type', 'application/json');
      res.setHeader('Cache-Control', `public, max-age=${Math.floor(cacheTtl / 1000)}`);
      res.json(enriched);
    } catch (err) {
      const error = err instanceof Error ? err : new Error(String(err));

      if (onError) {
        const fallback = onError(error);
        if (fallback === null) {
          next(error);
          return;
        }
        res.setHeader('Content-Type', 'application/json');
        res.json(fallback);
        return;
      }

      // Default fallback: serve the plain agent card without x-vaultfire
      console.error('[vaultfire-a2a] Enrichment failed, serving plain agent card:', error.message);
      res.setHeader('Content-Type', 'application/json');
      res.json(agentCard);
    }
  };
}

/**
 * Invalidate the middleware cache. Call this if you need to force a fresh
 * enrichment on the next request (e.g., after an on-chain event).
 *
 * Note: This factory creates a cache-invalidator for use when you hold a
 * reference to the cache. For most use cases the TTL is sufficient.
 */
export function createMiddlewareWithCache(options: VaultfireMiddlewareOptions): {
  middleware: RequestHandler;
  invalidate: () => void;
} {
  let cache: CacheEntry | null = null;
  let inflightPromise: Promise<EnrichedAgentCard> | null = null;
  const { agentCard, chain, agentAddress, cacheTtl = 5 * 60 * 1000, onError } = options;

  const middleware: RequestHandler = async (
    _req: Request,
    res: Response,
    next: NextFunction
  ): Promise<void> => {
    if (cache && Date.now() < cache.expiresAt) {
      res.setHeader('Content-Type', 'application/json');
      res.setHeader('Cache-Control', `public, max-age=${Math.floor(cacheTtl / 1000)}`);
      res.json(cache.card);
      return;
    }

    if (!inflightPromise) {
      inflightPromise = enrichAgentCard({ agentCard, chain, agentAddress }).finally(() => {
        inflightPromise = null;
      });
    }

    try {
      const enriched = await inflightPromise;
      if (cacheTtl > 0) {
        cache = { card: enriched, expiresAt: Date.now() + cacheTtl };
      }
      res.setHeader('Content-Type', 'application/json');
      res.setHeader('Cache-Control', `public, max-age=${Math.floor(cacheTtl / 1000)}`);
      res.json(enriched);
    } catch (err) {
      const error = err instanceof Error ? err : new Error(String(err));
      if (onError) {
        const fallback = onError(error);
        if (fallback === null) {
          next(error);
          return;
        }
        res.setHeader('Content-Type', 'application/json');
        res.json(fallback);
        return;
      }
      console.error('[vaultfire-a2a] Enrichment failed, serving plain agent card:', error.message);
      res.setHeader('Content-Type', 'application/json');
      res.json(agentCard);
    }
  };

  return {
    middleware,
    invalidate: () => {
      cache = null;
    },
  };
}
