import type { BotConfig } from "../../config.ts";
import type { ChannelStatus, ModeratorStatus } from "../../types.ts";
import { KickOAuthClient } from "../KickOAuthClient.ts";
import { SessionVault } from "../../session/SessionVault.ts";
import type { AdapterSessionStatus, KickSessionAdapter } from "./KickSessionAdapter.ts";

const EVENT_TYPES = [
  "chat.message.sent",
  "channel.followed",
  "channel.subscription.new",
  "channel.subscription.renewal",
  "channel.subscription.gifts",
  "channel.reward.redemption.updated",
  "livestream.status.updated",
  "livestream.metadata.updated",
  "moderation.banned",
  "kicks.gifted"
] as const;

export class OfficialKickApiAdapter implements KickSessionAdapter {
  private readonly config: BotConfig;
  private readonly client: KickOAuthClient;
  private subscriptionsCheckedAt = 0;
  private botSubscriptionCheckedAt = 0;

  constructor(config: BotConfig, vault: SessionVault) {
    this.config = config;
    this.client = new KickOAuthClient(config, vault);
  }

  async initialize(): Promise<void> {
    await this.client.initialize();
  }

  async close(): Promise<void> {
  }

  async testSession(): Promise<AdapterSessionStatus> {
    try {
      const identity = await this.client.identity();
      const valid = identity.username.toLocaleLowerCase("fr") === this.config.botUsername.toLocaleLowerCase("fr");
      return {
        valid,
        accountUsername: identity.username,
        kickUserId: identity.userId,
        expiresAt: this.client.session?.expiresAt ?? null,
        ...(valid ? {} : { error: `Le jeton appartient à ${identity.username || "un compte inconnu"}.` })
      };
    } catch (error) {
      return {
        valid: false,
        accountUsername: "",
        kickUserId: "",
        expiresAt: this.client.session?.expiresAt ?? null,
        error: error instanceof Error ? error.message : "Autorisation Kick invalide."
      };
    }
  }

  async sendChatMessage(channel: ChannelStatus, message: string): Promise<void> {
    const content = message.trim();
    if (!content) throw new Error("Message vide refusé.");
    if (content.length > 500) throw new Error("Message Kick trop long.");
    const broadcasterUserId = Number(channel.kick_user_id);
    if (!Number.isSafeInteger(broadcasterUserId) || broadcasterUserId <= 0) {
      throw new Error(`Identifiant diffuseur Kick invalide pour ${channel.slug}.`);
    }
    await this.client.userRequest("/public/v1/chat", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        broadcaster_user_id: broadcasterUserId,
        content,
        type: "user"
      })
    });
  }

  async checkModeratorRole(_channel: ChannelStatus): Promise<ModeratorStatus> {
    return "unknown";
  }

  async ensureEventSubscriptions(channels: ChannelStatus[]): Promise<void> {
    if (Date.now() - this.subscriptionsCheckedAt < 10 * 60_000) return;
    this.subscriptionsCheckedAt = Date.now();
    const errors: string[] = [];
    for (const channel of channels) {
      const broadcasterUserId = Number(channel.kick_user_id);
      if (!Number.isSafeInteger(broadcasterUserId) || broadcasterUserId <= 0) continue;
      try {
        await this.ensureChannelSubscriptions(broadcasterUserId);
      } catch (error) {
        errors.push(`${channel.slug}: ${error instanceof Error ? error.message : "erreur Kick"}`);
      }
    }
    if (errors.length) throw new Error(errors.join(" | "));
  }

  async ensureBotChannelSubscription(broadcasterUserId: number): Promise<void> {
    if (!Number.isSafeInteger(broadcasterUserId) || broadcasterUserId <= 0) return;
    if (Date.now() - this.botSubscriptionCheckedAt < 10 * 60_000) return;
    await this.ensureBotUserSubscription(broadcasterUserId);
    this.botSubscriptionCheckedAt = Date.now();
  }

  private async ensureBotUserSubscription(broadcasterUserId: number): Promise<void> {
    const current = await this.client.userRequest(
      `/public/v1/events/subscriptions?broadcaster_user_id=${encodeURIComponent(String(broadcasterUserId))}`
    );
    const rows = Array.isArray(current.data) ? current.data : [];
    const exists = rows.some((row) => {
      const item = row as Record<string, unknown>;
      return String(item.event ?? "") === "chat.message.sent" && (Number(item.version) || 1) === 1;
    });
    if (exists) return;

    const result = await this.client.userRequest("/public/v1/events/subscriptions", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        events: [{ name: "chat.message.sent", version: 1 }],
        method: "webhook"
      })
    });
    const failures = (Array.isArray(result.data) ? result.data : [])
      .map((row) => row as Record<string, unknown>)
      .filter((row) => String(row.error ?? ""));
    if (failures.length) {
      throw new Error(`Abonnement webhook du bot incomplet : ${failures.map((row) => String(row.error)).join(", ")}`);
    }
  }

  private async ensureChannelSubscriptions(
    broadcasterUserId: number,
    eventTypes: readonly string[] = EVENT_TYPES
  ): Promise<void> {
    const current = await this.client.appRequest(
      `/public/v1/events/subscriptions?broadcaster_user_id=${encodeURIComponent(String(broadcasterUserId))}`
    );
    const rows = Array.isArray(current.data) ? current.data : [];
    const existing = new Set(rows.map((row) => {
      const item = row as Record<string, unknown>;
      return `${String(item.event ?? "")}:${Number(item.version) || 1}`;
    }));
    const missing = eventTypes
      .filter((name) => !existing.has(`${name}:1`))
      .map((name) => ({ name, version: 1 }));
    if (!missing.length) return;
    const result = await this.client.appRequest("/public/v1/events/subscriptions", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        broadcaster_user_id: broadcasterUserId,
        events: missing,
        method: "webhook"
      })
    });
    const failures = (Array.isArray(result.data) ? result.data : [])
      .map((row) => row as Record<string, unknown>)
      .filter((row) => String(row.error ?? ""));
    if (failures.length) {
      throw new Error(`Abonnement webhook Kick incomplet : ${failures.map((row) => String(row.error)).join(", ")}`);
    }
  }
}
