import { useDbContext } from "@/context/db";
import { deriveDeviceKey, getDeviceId } from "@/util/deviceId";
import { useCallback } from "react";

export interface RawLocalAccount {
  accountId: string;
  label: string;
  email: string;
  profilePicture: string;
  access: { data: number[]; iv: number[] };
  refresh: { data: number[]; iv: number[] };
  updatedAt: string;
}

export interface LocalAccount {
  accountId: string;
  label: string;
  email: string;
  profilePicture: string;
  access: string;
  refresh: string;
  updatedAt: string;
}

export interface CreateAccountRequest {
  accountId: string;
  label: string;
  email: string;
  profilePicture: string;
  access: string;
  refresh: string;
}

export const useAccountRepo = () => {
  const { db } = useDbContext();

  const getDeviceKey = useCallback(async () => {
    const deviceId = await getDeviceId();
    const deviceKey = await deriveDeviceKey(deviceId);

    return deviceKey;
  }, []);

  const encryptToken = useCallback(
    async (token: string) => {
      const encoder = new TextEncoder();

      const iv = crypto.getRandomValues(new Uint8Array(12));

      const deviceKey = await getDeviceKey();

      const cipherText = await crypto.subtle.encrypt(
        { name: "AES-GCM", iv },
        deviceKey,
        encoder.encode(token)
      );

      return {
        data: Array.from(new Uint8Array(cipherText)),
        iv: Array.from(iv),
      };
    },
    [getDeviceKey]
  );

  const decryptToken = useCallback(
    async (encrypted: { data: number[]; iv: number[] }) => {
      const decoder = new TextDecoder();

      const deviceKey = await getDeviceKey();

      const decrypted = await crypto.subtle.decrypt(
        {
          name: "AES-GCM",
          iv: new Uint8Array(encrypted.iv),
        },
        deviceKey,
        new Uint8Array(encrypted.data)
      );

      return decoder.decode(decrypted);
    },
    [getDeviceKey]
  );

  const save = useCallback(
    async (req: CreateAccountRequest) => {
      if (!db) throw new Error("No database connection");

      const access = await encryptToken(req.access);
      const refresh = await encryptToken(req.refresh);

      await db?.put?.("accounts", {
        accountId: req.accountId,
        label: req.label,
        email: req.email,
        profilePicture: req.profilePicture,
        access,
        refresh,
        updatedAt: new Date().toISOString(),
      });
    },
    [db, encryptToken]
  );

  const loadAll = useCallback(async () => {
    if (!db) throw new Error("No database connection");

    const tx = db.transaction("accounts", "readonly");
    const store = tx.objectStore("accounts");

    const accounts: RawLocalAccount[] = await store.getAll();

    const results: LocalAccount[] = (
      await Promise.all(
        accounts.map(async (account) => {
          try {
            const accessToken = await decryptToken(account.access);
            const refreshToken = await decryptToken(account.refresh);
            return {
              accountId: account.accountId,
              label: account.label,
              email: account.email,
              profilePicture: account.profilePicture,
              access: accessToken,
              refresh: refreshToken,
              updatedAt: account.updatedAt,
            };
          } catch (err) {
            console.warn(`Failed to decrypt account ${account.label}:`, err);
          }
        })
      )
    ).filter((acc) => acc !== undefined);

    return results;
  }, [db, decryptToken]);

  return { save, loadAll };
};
