import type { ReceiptRedisExecutor, V3ReceiptRequirement } from './receipt-store';
import { createHash } from 'node:crypto';
import {
  canonicalEvidencePayload,
  paymentRequirementDigest,
  type V3CapabilityAuthorization,
  type V3EvidenceKind,
} from './capabilities';

/**
 * Deterministic adapter for tests only. Production code never imports it and
 * createTestReceiptRedis refuses to run outside an explicit test runtime.
 */
class TestReceiptRedis implements ReceiptRedisExecutor {
  private readonly values = new Map<string, string>();

  async get(key: string): Promise<string | null> {
    return this.values.get(key) ?? null;
  }

  async eval(_script: string, keys: string[], args: string[]): Promise<unknown> {
    if (keys.length === 4) {
      const existing = this.values.get(keys[0]);
      if (existing) {
        const receipt = JSON.parse(existing);
        return [{ result: JSON.stringify(receipt.bindingDigest === args[0]
          ? { outcome: 'existing', receipt }
          : { outcome: 'conflict', code: 'binding_conflict' }) }];
      }
      const codes = ['payment_replayed', 'authorization_replayed', 'nonce_replayed'];
      for (let i = 1; i < 4; i += 1) {
        if (this.values.has(keys[i])) return [{ result: JSON.stringify({ outcome: 'conflict', code: codes[i - 1] }) }];
      }
      const receipt = JSON.parse(args[4]);
      this.values.set(keys[0], args[4]);
      for (let i = 1; i < 4; i += 1) this.values.set(keys[i], receipt.receiptId);
      return [{ result: JSON.stringify({ outcome: 'claimed', receipt }) }];
    }
    const existing = this.values.get(keys[0]);
    if (!existing) return [{ result: JSON.stringify({ outcome: 'missing' }) }];
    const current = JSON.parse(existing);
    if (current.version !== Number(args[0])) {
      return [{ result: JSON.stringify({ outcome: 'conflict', code: 'version_conflict' }) }];
    }
    if (keys.length === 1 && !JSON.parse(args[1]).includes(current.state)) {
      return [{ result: JSON.stringify({ outcome: 'conflict', code: 'state_conflict' }) }];
    }
    const nextIndex = keys.length === 1 ? 2 : 1;
    if (keys.length === 1) {
      const next = JSON.parse(args[2]);
      const now = Number(args[4]);
      if ((now >= current.binding.expiresAt && next.state !== 'expired') ||
          (now < current.binding.expiresAt && next.state === 'expired')) {
        return [{ result: JSON.stringify({ outcome: 'conflict', code: 'state_conflict' }) }];
      }
    }
    if (keys.length === 2) {
      if (Number(args[3]) >= current.binding.expiresAt && ['claimed', 'pending_confirmation'].includes(current.state)) {
        this.values.set(keys[0], args[4]);
        return [{ result: JSON.stringify({ outcome: 'updated', receipt: JSON.parse(args[4]) }) }];
      }
      if (args[5] === '1') {
        return [{ result: JSON.stringify({ outcome: 'existing', receipt: current }) }];
      }
      const owner = this.values.get(keys[1]);
      if (owner && owner !== current.receiptId) {
        return [{ result: JSON.stringify({ outcome: 'conflict', code: 'transaction_replayed' }) }];
      }
      this.values.set(keys[1], current.receiptId);
    }
    this.values.set(keys[0], args[nextIndex]);
    return [{ result: JSON.stringify({ outcome: 'updated', receipt: JSON.parse(args[nextIndex]) }) }];
  }
}

export function createTestReceiptRedis(environment: Readonly<Record<string, string | undefined>> = process.env) {
  if (environment.NODE_ENV !== 'test') throw new Error('test receipt adapter is unavailable outside NODE_ENV=test');
  return new TestReceiptRedis();
}

/** Deterministic coherently signed capability bundle for isolated unit tests only. */
export function createTestReceiptCapabilities(
  now: number,
  requirement: V3ReceiptRequirement,
  concreteResource: string,
  validAfter = now - 1,
  expiresAt = now + 60_000,
) {
  const kinds: readonly V3EvidenceKind[] = ['deployment', 'listing', 'durable-store', 'finality-verifier', 'audit-anchor', 'payee'];
  const evidenceDigests = Object.fromEntries(kinds.map((kind) =>
    [kind, createHash('sha256').update(`test:${kind}:${requirement.id}`).digest('hex')]
  )) as Record<V3EvidenceKind, string>;
  const core = {
    schemaVersion: '1.0.0' as const,
    routeId: requirement.id,
    routeTemplate: requirement.route,
    concreteResource,
    method: requirement.method,
    network: requirement.network,
    chainId: requirement.chainId,
    deploymentAddress: requirement.deploymentAddress,
    deploymentCodeDigest: requirement.deploymentCodeDigest,
    asset: requirement.asset,
    payee: requirement.payee,
    amount: requirement.amount,
    scheme: requirement.scheme,
    requiredFinality: requirement.requiredFinality,
    minConfirmations: requirement.minConfirmations,
    validAfter,
    expiresAt,
  };
  const bundle: V3CapabilityAuthorization = {
    ...core,
    bundleId: `test-capability-${requirement.id}-0123456789`,
    environment: 'production',
    paymentRequirementDigest: paymentRequirementDigest(core),
    listingReceiptId: 'test-listing-receipt-0123456789',
    deploymentEvidenceId: 'test-deployment-evidence-0123456789',
    evidenceDigests,
    issuer: 'vaultfire-test-verifier',
    signature: 'A'.repeat(43),
    bundleDigest: '',
  };
  bundle.bundleDigest = createHash('sha256').update(canonicalEvidencePayload(bundle)).digest('hex');
  return {
    capabilityEvidence: { bundle },
    evidenceVerifier: (candidate: Readonly<V3CapabilityAuthorization>) => Object.freeze({ ...candidate }),
  };
}
