import {
  eventSigningMessage,
  unsignedAgentEventSchema,
  type SignedAgentEvent,
  type UnsignedAgentEvent,
} from "@pons-agent/protocol";
import type { AgentConfig } from "./config.js";
import { AgentState } from "./state.js";

export class PublicEventPublisher {
  constructor(private readonly config: AgentConfig) {}

  get enabled() {
    return Boolean(
      this.config.account
      && this.config.publicEvents.agentId
      && this.config.publicEvents.endpoint,
    );
  }

  async publish(
    type: UnsignedAgentEvent["type"],
    status: UnsignedAgentEvent["status"],
    payload: Record<string, unknown>,
    txHash?: string,
  ) {
    if (!this.enabled) return null;
    const account = this.config.account!;
    const sequenceState = new AgentState(this.config.stateDbPath);
    const sequence = sequenceState.nextSequence();
    sequenceState.close();

    const unsigned = unsignedAgentEventSchema.parse({
      version: 1,
      eventId: crypto.randomUUID(),
      agentId: this.config.publicEvents.agentId,
      sequence,
      type,
      status,
      occurredAt: new Date().toISOString(),
      chainId: 4663,
      ...(txHash ? { txHash } : {}),
      payload,
    });
    const signature = await account.signMessage({ message: eventSigningMessage(unsigned) });
    const event: SignedAgentEvent = { ...unsigned, signer: account.address, signature };

    const state = new AgentState(this.config.stateDbPath);
    state.enqueuePublicEvent(event);
    state.close();
    await this.deliver(event);
    return event;
  }

  async flushPending(limit = 100) {
    if (!this.enabled) return 0;
    const state = new AgentState(this.config.stateDbPath);
    const events = state.listPendingPublicEvents(limit);
    state.close();
    let delivered = 0;
    for (const event of events) {
      await this.deliver(event);
      delivered += 1;
    }
    return delivered;
  }

  private async deliver(event: SignedAgentEvent) {
    const markFailed = (error: unknown) => {
      const state = new AgentState(this.config.stateDbPath);
      state.markPublicEventFailed(event.eventId, error instanceof Error ? error.message : String(error));
      state.close();
    };

    try {
      const response = await fetch(this.config.publicEvents.endpoint!, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(event),
        signal: AbortSignal.timeout(45_000),
      });
      // A 409 means the endpoint already accepted this event before a retry.
      if (!response.ok && response.status !== 409) {
        throw new Error(`Public event endpoint ${response.status}: ${(await response.text()).slice(0, 500)}`);
      }
      const state = new AgentState(this.config.stateDbPath);
      state.markPublicEventSent(event.eventId);
      state.close();
    } catch (error) {
      markFailed(error);
      throw error;
    }
  }
}
