import type { Session, User } from '@supabase/supabase-js';
import { auth, crypto } from '@chat-app/shared';
import type { DeviceRecord } from '@chat-app/shared/auth';
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { Platform } from 'react-native';

import { secretStore } from './secretStore';
import { supabase } from './supabase';

// Locally-stored secrets keyed by stable names. Mirrors the desktop
// convention so the migration tests (later) can compare snapshots.
const KEY_DEVICE_ID = 'device.id';
const KEY_DEVICE_PRIVKEY = 'device.privateKey';

interface AuthContextValue {
  session: Session | null;
  user: User | null;
  device: DeviceRecord | null;
  ownPrivateKey: Uint8Array | null;
  loading: boolean;
  signOut: () => Promise<void>;
}

const Ctx = createContext<AuthContextValue | null>(null);

export function useAuth(): AuthContextValue {
  const v = useContext(Ctx);
  if (!v) throw new Error('useAuth() called outside <AuthProvider>');
  return v;
}

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [session, setSession] = useState<Session | null>(null);
  const [device, setDevice] = useState<DeviceRecord | null>(null);
  const [ownPrivateKey, setOwnPrivateKey] = useState<Uint8Array | null>(null);
  const [loading, setLoading] = useState(true);

  // Resolve or create the device record for this install given an active
  // session. Stores the private key in expo-secure-store on first run.
  const ensureDevice = useCallback(async (_currentSession: Session): Promise<void> => {
    const savedDeviceId = await secretStore.getSecret(KEY_DEVICE_ID);
    const savedPrivKey = await secretStore.getSecret(KEY_DEVICE_PRIVKEY);

    if (savedDeviceId && savedPrivKey) {
      const devices = await auth.listOwnDevices(supabase);
      const deviceIdStr = new TextDecoder().decode(savedDeviceId);
      const match = devices.find((d) => d.id === deviceIdStr);
      if (match) {
        setDevice(match);
        setOwnPrivateKey(savedPrivKey);
        return;
      }
      // Stored id no longer matches any device on the server (revoked,
      // wiped). Fall through to register a fresh one.
    }

    const backend = crypto.getCryptoBackend();
    const kp = backend.generateKeyPair();
    const platform = Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'linux';
    const record = await auth.registerDevice(supabase, {
      name: `Netralax Mobile (${Platform.OS})`,
      platform,
      publicKey: kp.publicKey,
    });
    await secretStore.setSecret(KEY_DEVICE_ID, new TextEncoder().encode(record.id));
    await secretStore.setSecret(KEY_DEVICE_PRIVKEY, kp.privateKey);
    setDevice(record);
    setOwnPrivateKey(kp.privateKey);
  }, []);

  useEffect(() => {
    let cancelled = false;
    void (async () => {
      const { data } = await supabase.auth.getSession();
      if (cancelled) return;
      setSession(data.session);
      if (data.session) {
        try {
          await ensureDevice(data.session);
        } catch (err) {
          console.warn('[auth] ensureDevice failed', err);
        }
      }
      setLoading(false);
    })();

    const { data: sub } = supabase.auth.onAuthStateChange((_event, nextSession) => {
      setSession(nextSession);
      if (!nextSession) {
        setDevice(null);
        setOwnPrivateKey(null);
      } else {
        void ensureDevice(nextSession).catch((err) =>
          console.warn('[auth] ensureDevice (state change) failed', err),
        );
      }
    });

    return () => {
      cancelled = true;
      sub.subscription.unsubscribe();
    };
  }, [ensureDevice]);

  const signOut = useCallback(async (): Promise<void> => {
    await supabase.auth.signOut();
    await secretStore.removeSecret(KEY_DEVICE_ID);
    await secretStore.removeSecret(KEY_DEVICE_PRIVKEY);
    setDevice(null);
    setOwnPrivateKey(null);
  }, []);

  const value: AuthContextValue = {
    session,
    user: session?.user ?? null,
    device,
    ownPrivateKey,
    loading,
    signOut,
  };
  return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
}
