const TOKEN_STORAGE_KEY = 'tenant_admin.token.v1';
const TOKEN_SESSION_KEY = 'tenant_admin.token.session.v1';

export type StoredToken = {
  accessToken: string;
  abilities: string[];
  issuedAt: number;
};

export class AuthError extends Error {
  constructor(public code: 'unauthenticated' | 'unauthorized', message?: string) {
    super(message ?? code);
    this.name = 'AuthError';
  }
}

let cachedToken: StoredToken | null = null;

function getCsrfToken(): string | undefined {
  const meta = typeof document !== 'undefined'
    ? (document.querySelector('meta[name=\"csrf-token\"]') as HTMLMetaElement | null)
    : null;
  return meta?.content;
}

function decodeStoredToken(raw: string | null): StoredToken | null {
  if (!raw) {
    return null;
  }

  try {
    const parsed = JSON.parse(raw) as StoredToken;
    if (!parsed || typeof parsed.accessToken !== 'string') {
      return null;
    }

    return {
      accessToken: parsed.accessToken,
      abilities: Array.isArray(parsed.abilities) ? parsed.abilities : [],
      issuedAt: typeof parsed.issuedAt === 'number' ? parsed.issuedAt : Date.now(),
    } satisfies StoredToken;
  } catch (error) {
    if (import.meta.env.DEV) {
      console.warn('[Auth] Failed to decode stored token', error);
    }
    return null;
  }
}

function readTokenFromStorage(): StoredToken | null {
  if (typeof window === 'undefined') {
    return null;
  }

  try {
    const sessionValue = window.sessionStorage.getItem(TOKEN_SESSION_KEY);
    const fromSession = decodeStoredToken(sessionValue);
    if (fromSession) {
      return fromSession;
    }
  } catch (error) {
    if (import.meta.env.DEV) {
      console.warn('[Auth] Failed to read session stored token', error);
    }
  }

  try {
    const persistentValue = window.localStorage.getItem(TOKEN_STORAGE_KEY);
    return decodeStoredToken(persistentValue);
  } catch (error) {
    if (import.meta.env.DEV) {
      console.warn('[Auth] Failed to read persisted token', error);
    }
    return null;
  }
}

export function loadToken(): StoredToken | null {
  if (cachedToken) {
    return cachedToken;
  }

  const stored = readTokenFromStorage();
  cachedToken = stored;

  return stored;
}

function persistToken(token: StoredToken): void {
  if (typeof window === 'undefined') {
    cachedToken = token;
    return;
  }

  const serialized = JSON.stringify(token);

  try {
    window.localStorage.setItem(TOKEN_STORAGE_KEY, serialized);
  } catch (error) {
    if (import.meta.env.DEV) {
      console.warn('[Auth] Failed to persist tenant token to localStorage', error);
    }
  }

  try {
    window.sessionStorage.setItem(TOKEN_SESSION_KEY, serialized);
  } catch (error) {
    if (import.meta.env.DEV) {
      console.warn('[Auth] Failed to persist tenant token to sessionStorage', error);
    }
  }

  cachedToken = token;
}

async function exchangeSessionForToken(): Promise<StoredToken | null> {
  const csrf = getCsrfToken();

  try {
    const response = await fetch('/api/v1/tenant-auth/exchange', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'X-Requested-With': 'XMLHttpRequest',
        ...(csrf ? { 'X-CSRF-TOKEN': csrf } : {}),
      },
      credentials: 'same-origin',
    });

    if (!response.ok) {
      return null;
    }

    const data = (await response.json()) as { token?: string; abilities?: string[] } | null;
    if (!data?.token) {
      return null;
    }

    return storePersonalAccessToken(data.token, Array.isArray(data.abilities) ? data.abilities : []);
  } catch (error) {
    if (import.meta.env.DEV) {
      console.warn('[Auth] Session token exchange failed', error);
    }
    return null;
  }
}

export function storePersonalAccessToken(accessToken: string, abilities: string[]): StoredToken {
  const stored: StoredToken = {
    accessToken,
    abilities,
    issuedAt: Date.now(),
  };

  persistToken(stored);

  return stored;
}

export function clearTokens(): void {
  cachedToken = null;

  if (typeof window === 'undefined') {
    return;
  }

  try {
    window.localStorage.removeItem(TOKEN_STORAGE_KEY);
  } catch (error) {
    if (import.meta.env.DEV) {
      console.warn('[Auth] Failed to remove stored tenant token', error);
    }
  }

  try {
    window.sessionStorage.removeItem(TOKEN_SESSION_KEY);
  } catch (error) {
    if (import.meta.env.DEV) {
      console.warn('[Auth] Failed to remove session tenant token', error);
    }
  }
}

type AuthFailureHandler = () => void;
const authFailureHandlers = new Set<AuthFailureHandler>();

function notifyAuthFailure(): void {
  authFailureHandlers.forEach((handler) => {
    try {
      handler();
    } catch (error) {
      console.error('[Auth] Failure handler threw', error);
    }
  });
}

export function registerAuthFailureHandler(handler: AuthFailureHandler): () => void {
  authFailureHandlers.add(handler);
  return () => {
    authFailureHandlers.delete(handler);
  };
}

export function isAuthError(value: unknown): value is AuthError {
  return value instanceof AuthError;
}

export async function authorizedFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {
  const headers = new Headers(init.headers);
  if (!headers.has('Accept')) {
    headers.set('Accept', 'application/json');
  }

  let stored = loadToken();
  if (!stored) {
    stored = await exchangeSessionForToken();
  }
  if (stored?.accessToken) {
    headers.set('Authorization', `Bearer ${stored.accessToken}`);
  }

  const response = await fetch(input, {
    ...init,
    headers,
    credentials: init.credentials ?? 'same-origin',
  });

  if (response.status === 401) {
    if (stored) {
      clearTokens();
    }
    notifyAuthFailure();
    throw new AuthError('unauthorized', 'Token rejected by API');
  }

  return response;
}
