import { signRequest } from "../security/hmac.ts";
import type { BotConfig } from "../config.ts";
import type { ChannelStatus, EngineEvent, InboundKickEvent, OutboundMessage } from "../types.ts";

export class PanelClient {
  readonly config: BotConfig;

  constructor(config: BotConfig) {
    this.config = config;
  }

  async channels(signal?: AbortSignal): Promise<ChannelStatus[]> {
    const payload = await this.request<{ channels: ChannelStatus[] }>("GET", "/api/worker/channels", undefined, signal);
    return payload.channels;
  }

  async channelConfig(channelId: number, signal?: AbortSignal): Promise<Record<string, unknown>> {
    return this.request("GET", `/api/worker/config?channel_id=${channelId}`, undefined, signal);
  }

  async queue(signal?: AbortSignal): Promise<OutboundMessage[]> {
    const payload = await this.request<{ messages: OutboundMessage[] }>("GET", "/api/worker/queue", undefined, signal);
    return payload.messages;
  }

  async acknowledge(message: OutboundMessage, status: "sent" | "retry" | "failed", error = ""): Promise<void> {
    await this.request("POST", `/api/worker/messages/${message.id}/ack`, {
      status,
      error: error.slice(0, 500),
      attempts: message.attempts
    });
  }

  async events(events: EngineEvent[]): Promise<void> {
    if (!events.length) return;
    await this.request("POST", "/api/worker/events", { events: events.slice(0, 100) });
  }

  async inboundEvents(signal?: AbortSignal): Promise<InboundKickEvent[]> {
    const payload = await this.request<{ events: InboundKickEvent[] }>(
      "GET",
      "/api/worker/inbound-events",
      undefined,
      signal
    );
    return payload.events;
  }

  async acknowledgeInboundEvent(
    id: number,
    status: "processed" | "retry" | "failed",
    error = ""
  ): Promise<void> {
    await this.request("POST", `/api/worker/inbound-events/${id}/ack`, {
      status,
      error: error.slice(0, 500)
    });
  }

  async heartbeat(data: Record<string, unknown>): Promise<void> {
    await this.request("POST", "/api/worker/heartbeat", {
      version: "0.1.0",
      ...data
    });
  }

  async sessionStatus(status: string, extra: Record<string, unknown> = {}): Promise<void> {
    await this.request("POST", "/api/worker/session-status", { status, ...extra });
  }

  async moderatorStatus(channelId: number, status: string, botKickUserId: string, error = ""): Promise<void> {
    await this.request("POST", "/api/worker/moderator-status", {
      channel_id: channelId,
      status,
      bot_kick_user_id: botKickUserId,
      error: error.slice(0, 500)
    });
  }

  async workerCommands(signal?: AbortSignal): Promise<Array<{ id: number; command_type: string; payload_json: string }>> {
    const payload = await this.request<{ commands: Array<{ id: number; command_type: string; payload_json: string }> }>(
      "GET",
      "/api/worker/commands",
      undefined,
      signal
    );
    return payload.commands;
  }

  async completeWorkerCommand(id: number, status: "completed" | "failed", result: string): Promise<void> {
    await this.request("POST", `/api/worker/commands/${id}/complete`, {
      status,
      result: result.slice(0, 1_000)
    });
  }

  private async request<T extends Record<string, unknown>>(
    method: string,
    pathAndQuery: string,
    data?: Record<string, unknown>,
    signal?: AbortSignal
  ): Promise<T> {
    const body = data ? JSON.stringify(data) : "";
    const url = new URL(pathAndQuery, this.config.panelUrl);
    const signedPath = url.pathname;
    const headers = {
      Accept: "application/json",
      ...(body ? { "Content-Type": "application/json" } : {}),
      ...signRequest(method, signedPath, body, this.config.workerId, this.config.workerSecret)
    };
    const init: RequestInit = { method, headers };
    if (body) init.body = body;
    if (signal) init.signal = signal;
    const response = await fetch(url, init);
    const payload = await response.json().catch(() => ({ ok: false, error: "Réponse panel invalide" })) as T & { ok?: boolean; error?: string };
    if (!response.ok || payload.ok === false) {
      throw new Error(payload.error || `Panel HTTP ${response.status}`);
    }
    return payload;
  }
}
