// ============================================================================
// H33 Agent SDK — Auto-attested agent wrapper
//
// The killer feature: wrap any existing agent in 5 minutes and every action
// becomes replayable, attestable, and independently verifiable.
//
// const agent = new H33Agent({ attested: true })
// ============================================================================

import { H33AgentClient, type H33ClientOptions } from './client.js';
import { H33Session } from './session.js';
import { H33Verifier } from './verify.js';
import { attestedTool, hashToolRequest, hashToolResponse, toolName } from './tools.js';
import { hashMemoryState, hashContextWindow } from './memory.js';
import { createHash } from 'crypto';
import type {
  AgentReceipt,
  RegisterAgentRequest,
  AgentType,
  RedactionLevel,
} from './types.js';

/** Configuration for H33Agent */
export interface H33AgentConfig {
  /** Enable automatic attestation for all actions. Default: true */
  attested?: boolean;
  /** API key. Falls back to H33_API_KEY env var */
  apiKey?: string;
  /** Base URL. Defaults to https://api.h33.ai */
  baseUrl?: string;
  /** Agent display name */
  name: string;
  /** Canonical name: h33.agent.<org>.<domain>.<role>.<env>.<seq> */
  canonicalName: string;
  /** Agent type */
  type?: AgentType;
  /** Tenant ID */
  tenantId: string;
  /** Allowed capabilities */
  capabilities?: string[];
  /** Session duration in seconds. Default: 3600 (1 hour) */
  sessionDurationSecs?: number;
  /** Allowed tools. Empty = all allowed */
  allowedTools?: string[];
  /** Forbidden data classes */
  forbiddenDataClasses?: string[];
  /** Max transaction value */
  maxTransactionValue?: number;
  /** Jurisdiction restriction */
  jurisdiction?: string;
  /** Redaction level for actions. Default: metadata_only */
  redactionLevel?: RedactionLevel;
  /** Auto-checkpoint memory every N actions. 0 = disabled. Default: 10 */
  memoryCheckpointInterval?: number;
}

/**
 * H33Agent — Auto-attested autonomous agent wrapper.
 *
 * Automatically:
 * - Registers agent identity (PQ keys)
 * - Starts attested sessions
 * - Signs every action
 * - Wraps every tool call
 * - Checkpoints memory
 * - Chains DAG nodes
 * - Emits proofs
 * - Closes sessions cleanly
 *
 * @example
 * ```ts
 * const agent = new H33Agent({
 *   attested: true,
 *   name: 'Claims Review Agent',
 *   canonicalName: 'h33.agent.acme.claims.review.prod.001',
 *   tenantId: 'acme-corp',
 * });
 *
 * await agent.start();
 *
 * // Every tool call is automatically attested
 * const search = agent.tool('h33.tool.acme.search.v1', async (query: string) => {
 *   return await database.search(query);
 * });
 * const { result, receipt } = await search('open claims');
 *
 * // Every action is automatically attested
 * const receipt = await agent.action('classify', 'Classified claim as high-risk', inputHash);
 *
 * // Memory checkpoints happen automatically (or manually)
 * await agent.checkpointMemory(contextWindow);
 *
 * // Verify any receipt — publicly, no API key needed
 * const valid = await agent.verify(receipt);
 *
 * // Clean session close with H33-74 commitment
 * await agent.stop();
 * ```
 */
export class H33Agent {
  private readonly config: Required<H33AgentConfig>;
  private readonly client: H33AgentClient;
  private readonly verifier: H33Verifier;

  private _agentId: string | null = null;
  private _agentReceipt: AgentReceipt | null = null;
  private _session: H33Session | null = null;
  private _receipts: AgentReceipt[] = [];
  private _actionsSinceCheckpoint: number = 0;
  private _started: boolean = false;

  constructor(config: H33AgentConfig) {
    this.config = {
      attested: true,
      apiKey: config.apiKey || process.env.H33_API_KEY || '',
      baseUrl: config.baseUrl || 'https://api.h33.ai',
      name: config.name,
      canonicalName: config.canonicalName,
      type: config.type || 'autonomous',
      tenantId: config.tenantId,
      capabilities: config.capabilities || ['execute', 'use_tools', 'read_memory'],
      sessionDurationSecs: config.sessionDurationSecs || 3600,
      allowedTools: config.allowedTools || [],
      forbiddenDataClasses: config.forbiddenDataClasses || [],
      maxTransactionValue: config.maxTransactionValue || 0,
      jurisdiction: config.jurisdiction || '',
      redactionLevel: config.redactionLevel || 'metadata_only',
      memoryCheckpointInterval: config.memoryCheckpointInterval ?? 10,
    } as Required<H33AgentConfig>;

    this.client = new H33AgentClient({
      apiKey: this.config.apiKey,
      baseUrl: this.config.baseUrl,
    });

    this.verifier = new H33Verifier(this.config.baseUrl);
  }

  // ── Lifecycle ─────────────────────────────────────────────────────────────

  /**
   * Start the agent — registers identity and opens an attested session.
   * Call this once at agent startup.
   */
  async start(): Promise<AgentReceipt> {
    if (this._started) throw new Error('Agent already started');

    // Register identity
    this._agentReceipt = await this.client.register({
      display_name: this.config.name,
      canonical_name: this.config.canonicalName,
      agent_type: this.config.type,
      capabilities: this.config.capabilities,
      tenant_id: this.config.tenantId,
    });
    this._agentId = this._agentReceipt.agent_id;

    // Start session
    const sessionReceipt = await this.client.startSession({
      agent_id: this._agentId,
      duration_secs: this.config.sessionDurationSecs,
      allowed_tools: this.config.allowedTools,
      forbidden_data_classes: this.config.forbiddenDataClasses,
      max_transaction_value: this.config.maxTransactionValue || undefined,
      jurisdiction: this.config.jurisdiction || undefined,
    });

    this._session = new H33Session(this.client, sessionReceipt);
    this._started = true;
    this._receipts.push(this._agentReceipt, sessionReceipt);

    return sessionReceipt;
  }

  /**
   * Stop the agent — closes the session cleanly with H33-74 commitment.
   * Call this when the agent's work is done.
   */
  async stop(): Promise<AgentReceipt> {
    this.ensureStarted();
    const receipt = await this._session!.end();
    this._receipts.push(receipt);
    this._started = false;
    this._session = null;
    return receipt;
  }

  // ── Actions ───────────────────────────────────────────────────────────────

  /**
   * Attest an action. Automatically chains into the session DAG.
   *
   * @param actionType - Type of action (e.g., 'classify', 'query', 'generate')
   * @param summary    - Human-readable summary (redacted, never raw content)
   * @param inputHash  - SHA-256 hex of the input (use hashToolRequest())
   * @param outputHash - SHA-256 hex of the output (optional)
   */
  async action(
    actionType: string,
    summary: string,
    inputHash: string,
    outputHash?: string,
    metadata?: Record<string, string>,
  ): Promise<AgentReceipt> {
    this.ensureStarted();
    const receipt = await this._session!.attestAction(
      actionType, summary, inputHash,
      {
        output_hash: outputHash,
        redaction_level: this.config.redactionLevel,
        metadata: metadata ? JSON.parse(JSON.stringify(metadata)) : undefined,
      }
    );
    this._receipts.push(receipt);
    this._actionsSinceCheckpoint++;
    await this.maybeAutoCheckpoint();
    return receipt;
  }

  /**
   * Wrap a tool handler with automatic attestation.
   * Returns a function that attests every call automatically.
   *
   * @example
   * ```ts
   * const search = agent.tool('h33.tool.acme.search.v1', async (q: string) => {
   *   return await db.search(q);
   * });
   * const { result, receipt } = await search('open claims');
   * ```
   */
  tool<T, R>(
    name: string,
    handler: (input: T) => Promise<R>,
  ): (input: T) => Promise<{ result: R; receipt: AgentReceipt }> {
    this.ensureStarted();
    return attestedTool(this._session!, name, handler);
  }

  /**
   * Call a tool directly with attestation (no wrapping needed).
   *
   * @example
   * ```ts
   * const receipt = await agent.callTool('h33.tool.weather.v1', { city: 'Tampa' }, weatherResult);
   * console.log(receipt.verification_url);
   * ```
   */
  async callTool(
    name: string,
    request: unknown,
    response?: unknown,
    status: string = 'success',
  ): Promise<AgentReceipt> {
    this.ensureStarted();
    const receipt = await this._session!.attestTool(name, hashToolRequest(request), {
      response_hash: response ? hashToolResponse(response) : undefined,
      status: status as any,
    });
    this._receipts.push(receipt);
    this._actionsSinceCheckpoint++;
    await this.maybeAutoCheckpoint();
    return receipt;
  }

  // ── Memory ────────────────────────────────────────────────────────────────

  /**
   * Checkpoint current memory state. Creates a cryptographic snapshot
   * of the agent's context window and retrieved documents.
   *
   * @param contextWindow - Array of messages or any serializable context
   * @param retrievedDocs - RAG results (optional)
   */
  async checkpointMemory(
    contextWindow: unknown,
    retrievedDocs?: unknown,
    sizeBytes?: number,
  ): Promise<AgentReceipt> {
    this.ensureStarted();
    const memHash = hashMemoryState(contextWindow);
    const ctxHash = typeof contextWindow === 'string'
      ? createHash('sha256').update(contextWindow).digest('hex')
      : hashContextWindow(contextWindow as any);
    const docsHash = retrievedDocs ? hashMemoryState(retrievedDocs) : undefined;
    const size = sizeBytes || JSON.stringify(contextWindow).length;

    const receipt = await this._session!.checkpointMemory(memHash, ctxHash, size);
    this._receipts.push(receipt);
    this._actionsSinceCheckpoint = 0;
    return receipt;
  }

  // ── Governance ────────────────────────────────────────────────────────────

  /**
   * Evaluate whether an action is permitted by the current policy.
   * Returns the policy decision as an attested receipt.
   */
  async evaluatePolicy(
    actionType: string,
    targetResource: string,
    parameters?: Record<string, unknown>,
  ): Promise<AgentReceipt> {
    this.ensureStarted();
    return this.client.evaluatePolicy({
      session_id: this._session!.id,
      action_type: actionType,
      target_resource: targetResource,
      parameters: parameters || {},
    });
  }

  /**
   * Check data exposure status — produces a cryptographic attestation
   * of whether data was exposed during an operation.
   */
  async checkExposure(dataClass: string, operation: string): Promise<AgentReceipt> {
    this.ensureStarted();
    return this.client.checkExposure({
      session_id: this._session!.id,
      data_class: dataClass,
      operation,
    });
  }

  /**
   * Request human or quorum approval for an action.
   */
  async requestApproval(
    summary: string,
    approvers: string[],
    threshold: number = 1,
    timeoutSecs: number = 300,
  ): Promise<AgentReceipt> {
    this.ensureStarted();
    return this.client.requestApproval({
      session_id: this._session!.id,
      action_summary: summary,
      required_approvers: approvers,
      threshold,
      timeout_secs: timeoutSecs,
    });
  }

  /**
   * Delegate authority to a sub-agent.
   */
  async delegate(
    delegateAgentId: string,
    allowedTools: string[],
    durationSecs: number,
    maxValue?: number,
  ): Promise<AgentReceipt> {
    this.ensureStarted();
    return this.client.delegate({
      delegator_session_id: this._session!.id,
      delegate_agent_id: delegateAgentId,
      allowed_tools: allowedTools,
      max_transaction_value: maxValue,
      duration_secs: durationSecs,
    });
  }

  // ── Verification ──────────────────────────────────────────────────────────

  /**
   * Verify any receipt — publicly, no API key needed.
   * This is what makes it a product.
   *
   * @example
   * ```ts
   * const proof = await agent.verify(receipt);
   * console.log(proof.valid);              // true
   * console.log(proof.integrity.chain);    // "INTACT"
   * ```
   */
  async verify(receipt: AgentReceipt): Promise<{
    valid: boolean;
    integrity: { chain: string; signatures: string; replay: string };
    verificationUrl: string;
    cliCommand: string;
  }> {
    const nodeId = receipt.node_id || receipt.receipt_id;
    const proof = await this.verifier.verify(nodeId);
    return {
      valid: proof.verification_status === 'VERIFIED',
      integrity: {
        chain: proof.dag_integrity || 'UNKNOWN',
        signatures: proof.signature_status || 'UNKNOWN',
        replay: proof.replay_status || 'UNKNOWN',
      },
      verificationUrl: receipt.verification_url,
      cliCommand: `hats verify --node ${nodeId}`,
    };
  }

  // ── Replay ────────────────────────────────────────────────────────────────

  /**
   * Replay this agent's session at a specific timestamp.
   */
  async replay(atTimestamp?: string) {
    this.ensureStarted();
    return this.client.replay({
      session_id: this._session!.id,
      at_timestamp: atTimestamp,
    });
  }

  /**
   * Fork replay — "what would have happened if the policy was different?"
   */
  async replayFork(forkAtNode: string, modifiedPolicy?: unknown) {
    this.ensureStarted();
    return this.client.replayFork({
      session_id: this._session!.id,
      fork_at_node: forkAtNode,
      modified_policy: modifiedPolicy as any,
    });
  }

  // ── Accessors ─────────────────────────────────────────────────────────────

  /** Agent ID (hex) — available after start() */
  get agentId(): string {
    return this._agentId || '';
  }

  /** Current session */
  get session(): H33Session | null {
    return this._session;
  }

  /** All receipts produced by this agent */
  get receipts(): AgentReceipt[] {
    return [...this._receipts];
  }

  /** Whether the agent is currently running */
  get running(): boolean {
    return this._started;
  }

  /** Underlying client (for advanced usage) */
  get client_(): H33AgentClient {
    return this.client;
  }

  // ── Internal ──────────────────────────────────────────────────────────────

  private ensureStarted(): void {
    if (!this._started || !this._session) {
      throw new Error(
        'Agent not started. Call agent.start() first.'
      );
    }
  }

  private async maybeAutoCheckpoint(): Promise<void> {
    const interval = this.config.memoryCheckpointInterval;
    if (interval > 0 && this._actionsSinceCheckpoint >= interval) {
      // Auto-checkpoint with a hash of the receipt chain as memory state
      const chainHash = createHash('sha256')
        .update(this._receipts.map(r => r.node_hash || r.receipt_id).join(':'))
        .digest('hex');
      await this._session!.checkpointMemory(
        chainHash,
        chainHash,
        this._receipts.length * 100, // approximate
      );
      this._actionsSinceCheckpoint = 0;
    }
  }
}
