import browser from "webextension-polyfill";
import { getBsplusDeviceName } from "@/seqta/utils/bsplusDeviceName";
import { clearCloudPfpCache } from "@/seqta/utils/cloudPfpCache";
import { clearLastUploadedSnapshot } from "@/seqta/utils/cloudSettingsSync";

const REDIRECT_URI = "https://accounts.betterseqta.org/auth/bsplus/callback";

const STORAGE_KEYS = {
  clientId: "bsplus_client_id",
  accessToken: "bsplus_token",
  refreshToken: "bsplus_refresh_token",
  user: "bsplus_user",
} as const;

export type CloudUser = {
  id: string;
  email?: string;
  username?: string;
  displayName?: string;
  pfpUrl?: string;
  pfpHash?: string | null;
  admin_level?: number;
};

export type CloudAuthState = {
  isLoggedIn: boolean;
  user: CloudUser | null;
};

/** Callback invoked when auth state changes */
type Listener = { (state: CloudAuthState): void };

class CloudAuthService {
  private static instance: CloudAuthService;
  private listeners = new Set<Listener>();
  private _state: CloudAuthState = { isLoggedIn: false, user: null };

  private constructor() {
    void this.loadFromStorage();
    browser.storage.onChanged.addListener((changes, areaName) => {
      if (
        areaName === "local" &&
        (changes[STORAGE_KEYS.accessToken] ||
          changes[STORAGE_KEYS.user] ||
          changes[STORAGE_KEYS.clientId])
      ) {
        void this.loadFromStorage();
      }
    });
  }

  public static getInstance(): CloudAuthService {
    if (!CloudAuthService.instance) {
      CloudAuthService.instance = new CloudAuthService();
    }
    return CloudAuthService.instance;
  }

  public get state(): CloudAuthState {
    return this._state;
  }

  public subscribe(listener: Listener): () => void {
    this.listeners.add(listener);
    listener(this._state);
    return () => this.listeners.delete(listener);
  }

  private async loadFromStorage(): Promise<void> {
    const result = await browser.storage.local.get([
      STORAGE_KEYS.accessToken,
      STORAGE_KEYS.user,
    ]);
    const token = result[STORAGE_KEYS.accessToken] as string | undefined;
    const user = result[STORAGE_KEYS.user] as CloudUser | undefined;
    this._state = {
      isLoggedIn: !!token,
      user: user ?? null,
    };
    this.notify();
  }

  private notify(): void {
    for (const listener of this.listeners) {
      listener(this._state);
    }
  }

  /** Pull cloud settings backup after a fresh sign-in (matches manual “Download from cloud”). */
  private triggerCloudSettingsDownloadAfterLogin(): void {
    void browser.runtime
      .sendMessage({
        type: "cloudSettingsDownload",
      })
      .then((res: unknown) => {
        const r = res as { success?: boolean; notFound?: boolean; error?: string } | undefined;
        if (r?.success || r?.notFound) return;
        if (r?.error) {
          console.warn("[BetterSEQTA+] Cloud settings download after login:", r.error);
        }
      })
      .catch((err) => {
        console.warn("[BetterSEQTA+] Cloud settings download after login failed:", err);
      });
  }

  public async getStoredToken(): Promise<string | null> {
    const result = await browser.storage.local.get(STORAGE_KEYS.accessToken);
    return (result[STORAGE_KEYS.accessToken] as string) ?? null;
  }

  /** Persist an updated user object (e.g. after cloud profile picture sync). */
  public async setUser(user: CloudUser | null): Promise<void> {
    await browser.storage.local.set({ [STORAGE_KEYS.user]: user });
    this._state = {
      isLoggedIn: this._state.isLoggedIn,
      user,
    };
    this.notify();
  }

  private async getClientId(): Promise<string> {
    const stored = await browser.storage.local.get(STORAGE_KEYS.clientId);
    let clientId = stored[STORAGE_KEYS.clientId] as string | undefined;
    if (!clientId) {
      const reserveResult = (await browser.runtime.sendMessage({
        type: "cloudReserveClient",
        redirect_uri: REDIRECT_URI,
      })) as { client_id?: string; error?: string };
      if (!reserveResult?.client_id) {
        throw new Error(reserveResult?.error ?? "Failed to reserve client");
      }
      clientId = reserveResult.client_id;
      await browser.storage.local.set({ [STORAGE_KEYS.clientId]: clientId });
    }
    return clientId;
  }

  public async startLogin(): Promise<{ success: boolean; error?: string }> {
    try {
      const clientId = await this.getClientId();
      const result = (await browser.runtime.sendMessage({
        type: "cloudStartLogin",
        client_id: clientId,
        redirect_uri: REDIRECT_URI,
      })) as { success?: boolean; error?: string };
      if (result?.success) {
        return { success: true };
      }
      return { success: false, error: result?.error ?? "Failed to open login page" };
    } catch (err) {
      return {
        success: false,
        error: err instanceof Error ? err.message : "Failed to open login page",
      };
    }
  }

  public async login(
    login: string,
    password: string
  ): Promise<{ success: boolean; error?: string }> {
    try {
      const clientId = await this.getClientId();
      const device_name = await getBsplusDeviceName();
      const result = (await browser.runtime.sendMessage({
        type: "cloudLogin",
        client_id: clientId,
        redirect_uri: REDIRECT_URI,
        login: login.trim(),
        password,
        device_name,
      })) as {
        access_token?: string;
        refresh_token?: string;
        user?: CloudUser;
        error?: string;
      };
      if (result?.access_token && result?.refresh_token) {
        await browser.storage.local.set({
          [STORAGE_KEYS.accessToken]: result.access_token,
          [STORAGE_KEYS.refreshToken]: result.refresh_token,
          [STORAGE_KEYS.user]: result.user ?? null,
        });
        this._state = {
          isLoggedIn: true,
          user: result.user ?? null,
        };
        this.notify();
        this.triggerCloudSettingsDownloadAfterLogin();
        return { success: true };
      }
      return {
        success: false,
        error: result?.error ?? "Login failed",
      };
    } catch (err) {
      return {
        success: false,
        error: err instanceof Error ? err.message : "Login failed",
      };
    }
  }

  public async logout(): Promise<void> {
    const userId = this._state.user?.id;
    if (userId) await clearCloudPfpCache(userId);
    await clearLastUploadedSnapshot();
    await browser.storage.local.remove([
      STORAGE_KEYS.accessToken,
      STORAGE_KEYS.refreshToken,
      STORAGE_KEYS.user,
      "cloudAccessToken",
      "cloudUsername",
    ]);
    this._state = { isLoggedIn: false, user: null };
    this.notify();
  }

  public async refreshToken(): Promise<boolean> {
    const result = await browser.storage.local.get([
      STORAGE_KEYS.refreshToken,
      STORAGE_KEYS.clientId,
    ]);
    const refreshToken = result[STORAGE_KEYS.refreshToken] as string | undefined;
    const clientId = result[STORAGE_KEYS.clientId] as string | undefined;
    if (!refreshToken || !clientId) return false;

    const refreshResult = (await browser.runtime.sendMessage({
      type: "cloudRefresh",
      refresh_token: refreshToken,
      client_id: clientId,
    })) as {
      access_token?: string;
      refresh_token?: string;
      user?: CloudUser;
      error?: string;
    };

    if (refreshResult?.access_token && refreshResult?.refresh_token) {
      await browser.storage.local.set({
        [STORAGE_KEYS.accessToken]: refreshResult.access_token,
        [STORAGE_KEYS.refreshToken]: refreshResult.refresh_token,
        [STORAGE_KEYS.user]: refreshResult.user ?? null,
      });
      this._state = {
        isLoggedIn: true,
        user: refreshResult.user ?? null,
      };
      this.notify();
      return true;
    }
    return false;
  }
}

export const cloudAuth = CloudAuthService.getInstance();
