import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import type { Locale } from '@ai-radar/shared';
import { apiDelete, apiPost, apiGet, ApiError } from './api';

/**
 * Accounts, on the app.
 *
 * Signing in is optional and always will be — the app is fully usable
 * without it, and nothing here gates a screen. What an account buys is
 * that filters and Saved follow the reader to the website and back.
 *
 * The session travels as a Bearer header rather than a cookie, because
 * React Native cannot use cookies cleanly. Same token, same row, same
 * revocation as the web; only the transport differs.
 */

const TOKEN_KEY = 'airadar.session';

export interface Account {
  id: number;
  email: string;
  displayName: string | null;
  emailVerified: boolean;
  locale: string;
}

interface AuthStore {
  account: Account | null;
  /** False until the stored token has been checked. */
  ready: boolean;
  signIn: (email: string, password: string) => Promise<{ ok: boolean; error?: string }>;
  register: (email: string, password: string, locale: Locale) => Promise<{ ok: boolean; error?: string }>;
  signOut: () => Promise<void>;
  deleteAccount: () => Promise<{ ok: boolean; error?: string }>;
}

const AuthContext = createContext<AuthStore | null>(null);

/** The Bearer header for the current session, if any. */
export async function authHeader(): Promise<Record<string, string>> {
  try {
    const token = await AsyncStorage.getItem(TOKEN_KEY);
    return token ? { authorization: `Bearer ${token}` } : {};
  } catch {
    return {};
  }
}

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [account, setAccount] = useState<Account | null>(null);
  const [ready, setReady] = useState(false);

  // Resolve the stored token once at startup. A token that no longer
  // works is treated as signed out rather than an error: sessions are
  // revocable, so this is an expected outcome.
  useEffect(() => {
    (async () => {
      try {
        const token = await AsyncStorage.getItem(TOKEN_KEY);
        if (token) {
          const me = await apiGet<{ account: Account | null }>('/auth/me', {
            authorization: `Bearer ${token}`,
          });
          setAccount(me.account);
          if (!me.account) await AsyncStorage.removeItem(TOKEN_KEY);
        }
      } catch {
        // Offline at launch. Keep the token — it may still be valid —
        // and treat the reader as signed out for now rather than
        // destroying a working session over a dropped connection.
      } finally {
        setReady(true);
      }
    })();
  }, []);

  const signIn = useCallback(async (email: string, password: string) => {
    try {
      const result = await apiPost<{ ok: boolean; token?: string; error?: string }>(
        '/auth/login',
        { email, password, client: 'app' },
      );

      if (!result.ok || !result.token) {
        return { ok: false, error: result.error ?? 'Wrong email or password.' };
      }

      await AsyncStorage.setItem(TOKEN_KEY, result.token);

      const me = await apiGet<{ account: Account | null }>('/auth/me', {
        authorization: `Bearer ${result.token}`,
      });
      setAccount(me.account);

      return { ok: true };
    } catch (err) {
      return { ok: false, error: err instanceof ApiError ? err.message : 'Could not sign in.' };
    }
  }, []);

  const register = useCallback(async (email: string, password: string, locale: Locale) => {
    try {
      const result = await apiPost<{ ok: boolean; error?: string }>('/auth/register', {
        email,
        password,
        locale,
      });
      return result.ok ? { ok: true } : { ok: false, error: result.error };
    } catch (err) {
      return { ok: false, error: err instanceof ApiError ? err.message : 'Could not register.' };
    }
  }, []);

  const signOut = useCallback(async () => {
    const header = await authHeader();

    // Revoke server-side too, so the session dies now rather than when
    // it expires. A failure here still signs out locally.
    await apiPost('/auth/logout', {}, header).catch(() => {});
    await AsyncStorage.removeItem(TOKEN_KEY).catch(() => {});
    setAccount(null);
  }, []);

  const deleteAccount = useCallback(async () => {
    try {
      const header = await authHeader();
      const result = await apiDelete<{ ok: boolean; error?: string }>(
        '/auth/account',
        { confirm: true },
        header,
      );

      if (!result.ok) return { ok: false, error: result.error };

      await AsyncStorage.removeItem(TOKEN_KEY).catch(() => {});
      setAccount(null);
      return { ok: true };
    } catch (err) {
      return {
        ok: false,
        error: err instanceof ApiError ? err.message : 'Could not delete the account.',
      };
    }
  }, []);

  return (
    <AuthContext.Provider value={{ account, ready, signIn, register, signOut, deleteAccount }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth(): AuthStore {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used inside AuthProvider');
  return ctx;
}
