// Authentication state store with Rust backend
//
// All business logic (session management, verification, credential storage) is handled by Rust.
// This file is a thin Svelte store wrapper that calls Rust commands and listens to events.
//
// TRACES: UR-009, UR-012 | IR-009, IR-014

import { writable, derived, get } from "svelte/store";
import { listen } from "@tauri-apps/api/event";
import { commands } from "$lib/api/bindings";
import { RepositoryClient } from "$lib/api/repository-client";
import type { User, AuthResult } from "$lib/api/types";
import type { Session, AuthServerInfo as ServerInfo } from "$lib/api/bindings";
import { connectivity } from "./connectivity";
import { getDeviceId, clearCache as clearDeviceIdCache } from "$lib/services/deviceId";

interface AuthState {
  isAuthenticated: boolean;
  isLoading: boolean;
  user: User | null;
  serverUrl: string | null;
  serverName: string | null;
  error: string | null;
  securityWarning: string | null;
  /** Whether session needs re-authentication (e.g., token expired) */
  needsReauth: boolean;
  /** Whether session verification is in progress */
  isVerifying: boolean;
  /** Whether the session is known to be valid (verified with server) */
  sessionVerified: boolean;
}

function createAuthStore() {
  const initialState: AuthState = {
    isAuthenticated: false,
    isLoading: true,
    user: null,
    serverUrl: null,
    serverName: null,
    error: null,
    securityWarning: null,
    needsReauth: false,
    isVerifying: false,
    sessionVerified: false,
  };

  const { subscribe, set, update } = writable<AuthState>(initialState);

  // RepositoryClient provides cache-first access with automatic background refresh via Rust
  let repository: RepositoryClient | null = null;

  // Store unlisten functions for cleanup
  let unlistenSessionVerified: (() => void) | null = null;
  let unlistenNeedsReauth: (() => void) | null = null;
  let unlistenNetworkError: (() => void) | null = null;

  function getRepository(): RepositoryClient {
    if (!repository) {
      throw new Error("Not connected to a server");
    }
    return repository;
  }

  /**
   * Initialize event listeners from Rust backend.
   * These should be called once during app initialization.
   */
  async function initializeEventListeners(): Promise<void> {
    if (typeof window === "undefined") return;

    try {
      unlistenSessionVerified = await listen<{ user: User }>("auth:session-verified", (event) => {
        console.log("[Auth] Session verified:", event.payload.user.name);
        update((s) => ({
          ...s,
          sessionVerified: true,
          needsReauth: false,
          isVerifying: false,
          user: event.payload.user,
        }));
      });
    } catch (e) {
      console.error("[Auth] Failed to listen to session-verified event:", e);
    }

    try {
      unlistenNeedsReauth = await listen<{ reason: string }>("auth:needs-reauth", (event) => {
        console.log("[Auth] Session needs re-authentication:", event.payload.reason);
        update((s) => ({
          ...s,
          sessionVerified: false,
          needsReauth: true,
          isVerifying: false,
          error: event.payload.reason,
        }));
      });
    } catch (e) {
      console.error("[Auth] Failed to listen to needs-reauth event:", e);
    }

    try {
      unlistenNetworkError = await listen<{ message: string }>("auth:network-error", (event) => {
        console.log("[Auth] Network error during verification:", event.payload.message);
        // Network errors don't trigger re-auth - just log them
        update((s) => ({ ...s, isVerifying: false }));
      });
    } catch (e) {
      console.error("[Auth] Failed to listen to network-error event:", e);
    }
  }

  /**
   * Cleanup event listeners.
   * Should be called when the app is destroyed.
   */
  function cleanupEventListeners(): void {
    if (unlistenSessionVerified) {
      unlistenSessionVerified();
      unlistenSessionVerified = null;
    }
    if (unlistenNeedsReauth) {
      unlistenNeedsReauth();
      unlistenNeedsReauth = null;
    }
    if (unlistenNetworkError) {
      unlistenNetworkError();
      unlistenNetworkError = null;
    }
  }

  /**
   * Initialize auth state from Rust backend.
   * This function does NOT require network access - session is restored immediately.
   */
  async function initialize() {
    // Initialize event listeners first
    await initializeEventListeners();

    update((s) => ({ ...s, isLoading: true, error: null }));

    try {
      // Check security status — fire-and-forget. It only sets a warning banner,
      // so it must not sit in front of session restore (and thus first paint).
      void (async () => {
        try {
          const securityStatus = await commands.storageGetSecurityStatus();
          console.log("[Auth] Security status:", securityStatus);
          if (!securityStatus.usingKeyring) {
            update((s) => ({
              ...s,
              securityWarning:
                "Credentials are stored with reduced security (encrypted file instead of system keyring).",
            }));
          }
        } catch (error) {
          console.warn("[Auth] Failed to get security status:", error);
        }
      })();

      // Initialize auth manager and get session
      console.log("[Auth] Initializing auth manager...");
      const session = await commands.authInitialize();
      console.log("[Auth] Session retrieval result:", session ? "Session found" : "No session found");

      if (session) {
        console.log("[Auth] Restoring session for user:", session.username, "on server:", session.serverUrl);

        // Create RepositoryClient for cache-first access. This IS required before
        // we mark authenticated — the first screen (library overview) reads
        // through it — so keep it awaited.
        repository = new RepositoryClient();
        await repository.create(session.serverUrl, session.userId, session.accessToken, session.serverId);

        // Configure the Rust player for playback reporting. This is NOT needed to
        // render the first screen (it only matters once playback starts), so run
        // it fire-and-forget instead of blocking first paint on two more IPC
        // round-trips (getDeviceId + playerConfigureJellyfin).
        void (async () => {
          try {
            const deviceId = await getDeviceId();
            await commands.playerConfigureJellyfin(
              session.serverUrl,
              session.accessToken,
              session.userId,
              deviceId
            );
            console.log("[Auth] Rust player configured for automatic playback reporting");
          } catch (error) {
            console.error("[Auth] Failed to configure Rust player:", error);
          }
        })();

        // Set authenticated immediately (offline-first)
        set({
          isAuthenticated: true,
          isLoading: false,
          user: { id: session.userId, name: session.username, serverId: session.serverId } as User,
          serverUrl: session.serverUrl,
          serverName: session.serverName,
          error: null,
          securityWarning: initialState.securityWarning,
          needsReauth: session.needsReauth,
          isVerifying: false,
          sessionVerified: session.verified,
        });

        // Start connectivity monitoring early to avoid appearing offline on startup
        console.log("[Auth] Starting early connectivity monitoring...");
        connectivity.startMonitoring(session.serverUrl, {
          onServerReconnected: () => {
            // Retry session verification when server becomes reachable
            retryVerification();
            // Resume downloads queued while offline, then refresh the catalog.
            // Lazy import to avoid an auth <-> offlineCatalog import cycle.
            import("$lib/services/offlineCatalog")
              .then((m) => m.onReconnected())
              .catch((err) => console.warn("[Auth] Catalog reconnect failed:", err));
          },
        }).catch((error) => {
          console.error("[Auth] Failed to start connectivity monitoring:", error);
        });

        // Start background session verification — fire-and-forget. This is
        // already asynchronous work (results arrive via the auth:* events wired
        // above), so awaiting getDeviceId + authStartVerification here only
        // delayed first paint by two IPC round-trips for no UI benefit.
        void (async () => {
          try {
            const verifyDeviceId = await getDeviceId();
            await commands.authStartVerification(verifyDeviceId);
            console.log("[Auth] Background verification started");
          } catch (error) {
            console.error("[Auth] Failed to start verification:", error);
          }
        })();
      } else {
        // No stored session
        console.log("[Auth] No active session found");
        set({
          isAuthenticated: false,
          isLoading: false,
          user: null,
          serverUrl: null,
          serverName: null,
          error: null,
          securityWarning: initialState.securityWarning,
          needsReauth: false,
          isVerifying: false,
          sessionVerified: false,
        });
      }
    } catch (error) {
      console.error("[Auth] Failed to initialize:", error);
      update((s) => ({
        ...s,
        isLoading: false,
        error: error instanceof Error ? error.message : String(error),
      }));
    }
  }

  /**
   * Connect to a Jellyfin server and retrieve server info.
   * Rust will normalize the URL (add https:// if missing, remove trailing slash).
   *
   * TRACES: UR-009 | IR-009
   */
  async function connectToServer(serverUrl: string): Promise<ServerInfo> {
    update((s) => ({ ...s, isLoading: true, error: null }));

    try {
      console.log("[Auth] Connecting to server:", serverUrl);
      const serverInfo = await commands.authConnectToServer(serverUrl);
      console.log("[Auth] Connected to server:", serverInfo.name, serverInfo.version);
      console.log("[Auth] Normalized URL:", serverInfo.normalizedUrl);

      update((s) => ({ ...s, isLoading: false }));
      return serverInfo;
    } catch (error) {
      console.error("[Auth] Failed to connect to server:", error);
      update((s) => ({
        ...s,
        isLoading: false,
        error: error instanceof Error ? error.message : String(error),
      }));
      throw error;
    }
  }

  /**
   * Login with username and password.
   *
   * TRACES: UR-009, UR-012 | IR-009, IR-014
   */
  async function login(username: string, password: string, serverUrl: string, serverName: string) {
    update((s) => ({ ...s, isLoading: true, error: null }));

    try {
      const deviceId = await getDeviceId();
      console.log("[Auth] Logging in as:", username);

      const authResult = await commands.authLogin(serverUrl, username, password, deviceId);

      console.log("[Auth] Login successful:", authResult.user);

      // Save to storage
      await commands.storageSaveServer(authResult.serverId, serverName, serverUrl, null);

      await commands.storageSaveUser(
        authResult.user.id,
        authResult.serverId,
        authResult.user.name,
        authResult.accessToken
      );

      await commands.storageSetActiveUser(authResult.user.id, authResult.serverId);

      // Set session in auth manager with server name
      await commands.authSetSession({
        userId: authResult.user.id,
        username: authResult.user.name,
        serverId: authResult.serverId,
        serverUrl,
        serverName,
        accessToken: authResult.accessToken,
        verified: true,
        needsReauth: false,
      });

      // Create RepositoryClient
      repository = new RepositoryClient();
      await repository.create(serverUrl, authResult.user.id, authResult.accessToken, authResult.serverId);

      // Configure Rust player
      try {
        const playerDeviceId = await getDeviceId();
        await commands.playerConfigureJellyfin(
          serverUrl,
          authResult.accessToken,
          authResult.user.id,
          playerDeviceId
        );
        console.log("[Auth] Rust player configured for playback reporting");
      } catch (error) {
        console.error("[Auth] Failed to configure Rust player:", error);
      }

      // Update state
      set({
        isAuthenticated: true,
        isLoading: false,
        user: authResult.user,
        serverUrl,
        serverName,
        error: null,
        securityWarning: initialState.securityWarning,
        needsReauth: false,
        isVerifying: false,
        sessionVerified: true,
      });

      // Start background verification
      try {
        const verifyDeviceId = await getDeviceId();
        await commands.authStartVerification(verifyDeviceId);
      } catch (error) {
        console.error("[Auth] Failed to start verification:", error);
      }

      return authResult;
    } catch (error) {
      console.error("[Auth] Login failed:", error);
      const errorMessage = error instanceof Error ? error.message : String(error);
      update((s) => ({ ...s, isLoading: false, error: errorMessage }));
      throw error;
    }
  }

  /**
   * Re-authenticate with password (when session expired).
   */
  async function reauthenticate(password: string) {
    update((s) => ({ ...s, isLoading: true, error: null, needsReauth: false }));

    try {
      const deviceId = await getDeviceId();
      console.log("[Auth] Re-authenticating...");

      const authResult = await commands.authReauthenticate(password, deviceId);

      console.log("[Auth] Re-authentication successful");

      // Update storage
      await commands.storageSaveUser(
        authResult.user.id,
        authResult.serverId,
        authResult.user.name,
        authResult.accessToken
      );

      // Recreate repository with new credentials
      if (repository) {
        await repository.destroy();
        const session = await commands.authGetSession();
        if (session) {
          await repository.create(session.serverUrl, authResult.user.id, authResult.accessToken, authResult.serverId);
        }
      }

      // Reconfigure player
      try {
        const playerDeviceId = await getDeviceId();
        await commands.playerConfigureJellyfin(
          repository ? await getCurrentSessionServerUrl() : "",
          authResult.accessToken,
          authResult.user.id,
          playerDeviceId
        );
      } catch (error) {
        console.error("[Auth] Failed to reconfigure player:", error);
      }

      // Update state
      update((s) => ({
        ...s,
        isLoading: false,
        needsReauth: false,
        sessionVerified: true,
        user: authResult.user,
        error: null,
      }));

      return authResult;
    } catch (error) {
      console.error("[Auth] Re-authentication failed:", error);
      const errorMessage = error instanceof Error ? error.message : String(error);
      update((s) => ({ ...s, isLoading: false, error: errorMessage }));
      throw error;
    }
  }

  /**
   * Logout and clear session.
   *
   * TRACES: UR-012 | IR-014
   */
  async function logout() {
    try {
      const session = await commands.authGetSession();
      if (session) {
        const deviceId = await getDeviceId();
        await commands.authLogout(session.serverUrl, session.accessToken, deviceId);
      }

      // Disable Jellyfin reporting in player
      try {
        await commands.playerDisableJellyfin();
      } catch (error) {
        console.error("[Auth] Failed to disable player reporting:", error);
      }

      // Clear repository
      if (repository) {
        await repository.destroy();
      }
      repository = null;

      set({
        isAuthenticated: false,
        isLoading: false,
        user: null,
        serverUrl: null,
        serverName: null,
        error: null,
        securityWarning: null,
        needsReauth: false,
        isVerifying: false,
        sessionVerified: false,
      });

      // Clear device ID cache on logout
      clearDeviceIdCache();
    } catch (error) {
      console.error("[Auth] Logout error (continuing anyway):", error);
      set(initialState);
      clearDeviceIdCache();
    }
  }

  /**
   * Clear error state.
   */
  function clearError() {
    update((s) => ({ ...s, error: null }));
  }

  /**
   * Get current session from Rust backend.
   */
  async function getCurrentSession() {
    try {
      return await commands.authGetSession();
    } catch (error) {
      console.error("[Auth] Failed to get current session:", error);
      return null;
    }
  }

  /**
   * Get current user ID.
   */
  function getUserId(): string | null {
    const state = get({ subscribe });
    return state.user?.id || null;
  }

  /**
   * Get server URL.
   */
  function getServerUrl(): string | null {
    const state = get({ subscribe });
    return state.serverUrl;
  }

  /**
   * Helper to get server URL from current session.
   */
  async function getCurrentSessionServerUrl(): Promise<string> {
    const session = await commands.authGetSession();
    return session?.serverUrl || "";
  }

  /**
   * Retry session verification (called when server becomes reachable again).
   */
  async function retryVerification() {
    try {
      const deviceId = await getDeviceId();
      console.log("[Auth] Retrying session verification after reconnection");
      await commands.authStartVerification(deviceId);
    } catch (error) {
      console.error("[Auth] Failed to retry verification:", error);
    }
  }

  return {
    subscribe,
    initialize,
    connectToServer,
    login,
    reauthenticate,
    logout,
    clearError,
    getRepository,
    getCurrentSession,
    getUserId,
    getServerUrl,
    retryVerification,
    cleanupEventListeners,
  };
}

export const auth = createAuthStore();
export const isAuthenticated = derived(auth, ($auth) => $auth.isAuthenticated);
export const isLoading = derived(auth, ($auth) => $auth.isLoading);
export const currentUser = derived(auth, ($auth) => $auth.user);
export const needsReauth = derived(auth, ($auth) => $auth.needsReauth);
export const securityWarning = derived(auth, ($auth) => $auth.securityWarning);
export const authError = derived(auth, ($auth) => $auth.error);
export const isVerifying = derived(auth, ($auth) => $auth.isVerifying);
export const sessionVerified = derived(auth, ($auth) => $auth.sessionVerified);
export const serverName = derived(auth, ($auth) => $auth.serverName);
export const serverUrl = derived(auth, ($auth) => $auth.serverUrl);
