"use client";

import {
  GuestChatQuotaSchema,
  type GuestChatQuotaData,
} from "@/data/schemas/chat/guest_chat_quota";
import { GuestChatQuota as GuestChatQuotaDto } from "@/data/dto/chat/guest_chat_quota";
import { z } from "zod";

import { type Result as ResultT, Result, todayString, SpAsyncUtil } from "@/utils";
import { StorageKeys } from "../storage_keys";
import type { IChatStorage } from "./ichat_storage";

export class ChatStorage implements IChatStorage {
  private static _instance: ChatStorage | null = null;

  static getInstance(): ChatStorage {
    if (!ChatStorage._instance) ChatStorage._instance = new ChatStorage();
    return ChatStorage._instance;
  }

  /** @internal */
  static _resetForTests(): void {
    ChatStorage._instance = null;
  }

  // ---- guest daily chat quota ----

  async getGuestDailyChatQuota(): Promise<ResultT<GuestChatQuotaData | null>> {
    const existing = await SpAsyncUtil.getJson(
      StorageKeys.guestChatQuota,
      GuestChatQuotaSchema,
    );
    if (Result.isErr(existing)) return existing;

    const today = todayString();
    if (existing.data != null) {
      const quota = GuestChatQuotaDto.from(existing.data);
      if (!quota.needsReset(today)) return existing;
    }

    const initResult = await this.setGuestDailyChatQuota(
      GuestChatQuotaDto.threshold,
      today,
    );
    if (Result.isErr(initResult)) return existing;

    return {
      success: true,
      data: { remaining: GuestChatQuotaDto.threshold, date: today },
    };
  }

  setGuestDailyChatQuota(remaining: number, date: string): Promise<ResultT<void>> {
    const value: GuestChatQuotaData = { remaining, date };
    return SpAsyncUtil.setJson(StorageKeys.guestChatQuota, value, GuestChatQuotaSchema);
  }

  clearGuestDailyChatQuota(): Promise<ResultT<void>> {
    return SpAsyncUtil.remove(StorageKeys.guestChatQuota);
  }

  // ---- guest total quota ----

  async getGuestTotalQuota(): Promise<ResultT<number | null>> {
    const existing = await SpAsyncUtil.getJson(
      StorageKeys.guestTotalQuotaRemaining,
      z.number(),
    );
    if (Result.isErr(existing)) return existing;
    if (existing.data != null) return existing;

    const initResult = await this.setGuestTotalQuota(
      GuestChatQuotaDto.totalQuotaDefault,
    );
    if (Result.isErr(initResult)) return existing;

    return { success: true, data: GuestChatQuotaDto.totalQuotaDefault };
  }

  setGuestTotalQuota(remaining: number): Promise<ResultT<void>> {
    return SpAsyncUtil.setJson(StorageKeys.guestTotalQuotaRemaining, remaining, z.number());
  }

  clearGuestTotalQuota(): Promise<ResultT<void>> {
    return SpAsyncUtil.remove(StorageKeys.guestTotalQuotaRemaining);
  }
}
