import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import api from './api';
import { verifyPassword, cacheHash, initDB } from '../lib/db';
import { mintOfflineToken, cacheOfflineJwtSecret } from '../lib/offlineAuth';

interface User {
  id: number;
  email: string;
  role: 'school_admin' | 'systems_admin' | 'teacher' | 'student' | 'parent' | 'principal' | 'hr' | 'bursar' | 'librarian' | 'clubs_head' | 'driver' | 'groundsman' | 'matron' | 'boarding_master' | 'security' | 'janitor';
  // Phase 1 PR 1: list of roles the user effectively has right now
  // (primary + non-revoked, non-expired auxiliary grants). Used by
  // ProtectedRoute and Nav. Old clients that don't read this still work —
  // we fall back to the primary `role` everywhere.
  effective_roles?: string[];
  first_name: string;
  last_name: string;
  gender?: 'male' | 'female' | 'other' | null;
  profile_image?: string;
}

interface AuthState {
  token: string | null;
  user: User | null;
  offline: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: () => boolean;
  updateUser: (updatedUser: Partial<User>) => void;
  // Phase 1 PR 1: re-fetch the JWT and the user payload so freshly-granted
  // auxiliary roles show up without waiting 7 days for the original JWT
  // to expire. Safe to call on app load and after any user_roles mutation.
  refreshRoles: () => Promise<void>;
}

const looksOffline = (err: any): boolean => {
  if (!err) return false;
  return !err.response || err.code === 'ERR_NETWORK' || err.message?.includes('Network Error') || err.message?.includes('CONNECTION_REFUSED');
};

const computeEffectiveRoles = (user: User | null): string[] => {
  if (!user) return [];
  if (Array.isArray(user.effective_roles) && user.effective_roles.length > 0) {
    return user.effective_roles;
  }
  return user.role ? [user.role] : [];
};

export const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      token: null,
      user: null,
      offline: false,
      login: async (email: string, password: string) => {
        try {
          const res = await api.post('/auth/login', { email, password });
          const { token, user, offline_jwt_secret } = res.data;
          const userWithRoles: User = { ...user, effective_roles: computeEffectiveRoles(user) };
          set({ token, user: userWithRoles, offline: false });
          // Cache the JWT secret the server returned so we can mint offline
          // tokens later. If the server withholds it (prod default), we
          // clear any stale cached value.
          cacheOfflineJwtSecret(offline_jwt_secret ?? null);
          if (user?.email) {
            // Cache the caller's own password hash for offline auth. The
            // server never exposes passwords in the login response; this
            // authenticated call returns only the caller's own hash.
            try {
              await initDB();
              const credRes = await api.get('/auth/me/credentials');
              if (credRes.data?.password_hash) {
                await cacheHash(user.email, credRes.data.password_hash);
              }
            } catch (e) {
              console.warn('Failed to cache login hash for offline use', e);
            }
          }
          return;
        } catch (err: any) {
          if (!looksOffline(err)) {
            const msg = err.response?.data?.error || err.message || 'Login failed';
            throw new Error(msg);
          }
          // Offline path: verify against local SQLite.
          await initDB();
          const result = await verifyPassword(email, password);
          if (!result.ok) {
            throw new Error(result.reason || 'Invalid credentials (offline)');
          }
          const token = await mintOfflineToken(result.user);
          const userWithRoles: User = { ...result.user, effective_roles: computeEffectiveRoles(result.user) };
          set({ token, user: userWithRoles, offline: true });
          return;
        }
      },
      logout: () => {
        set({ token: null, user: null, offline: false });
      },
      isAuthenticated: () => !!get().token,
      updateUser: (updatedUser: Partial<User>) => {
        const currentUser = get().user;
        if (currentUser) {
          const merged: User = { ...currentUser, ...updatedUser };
          merged.effective_roles = computeEffectiveRoles(merged);
          set({ user: merged });
        }
      },
      refreshRoles: async () => {
        try {
          const res = await api.get('/auth/refresh-roles');
          const { token, user } = res.data;
          // /api/auth/refresh-roles now returns the full user payload
          // (id/email/role/effective_roles + first_name/last_name/profile_image),
          // so a profile edit on another tab/device is picked up here
          // without needing a manual refresh.
          const userWithRoles: User = { ...user, effective_roles: computeEffectiveRoles(user) };
          set({ token, user: userWithRoles, offline: false });
        } catch (err: any) {
          if (looksOffline(err)) {
            // Offline: keep the existing token, do nothing
            return;
          }
          throw err;
        }
      },
    }),
    {
      name: 'auth-storage',
      partialize: (state) => ({ token: state.token, user: state.user, offline: state.offline }),
    }
  )
);

// Helper: does the current user effectively have role X?
// Used by route gating and nav rendering. Falls back to the primary role
// when the JWT predates the auxiliary-roles feature.
export const hasEffectiveRole = (user: User | null, role: string): boolean => {
  if (!user) return false;
  if (user.role === role) return true;
  if (Array.isArray(user.effective_roles)) {
    return user.effective_roles.includes(role);
  }
  return false;
};

// API helper with auth (deprecated, use shared api instead)
export const useApi = () => api;

export { api };
export const apiInstance = api;
export default api;
