const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
const CSRF_STORAGE_KEY = 'piu.admin.csrfToken';

let csrfToken = null;
if (typeof window !== 'undefined' && window.sessionStorage) {
  csrfToken = window.sessionStorage.getItem(CSRF_STORAGE_KEY);
}

const persistCsrfToken = (token) => {
  csrfToken = token;
  if (typeof window !== 'undefined' && window.sessionStorage) {
    if (token) {
      window.sessionStorage.setItem(CSRF_STORAGE_KEY, token);
    } else {
      window.sessionStorage.removeItem(CSRF_STORAGE_KEY);
    }
  }
};

const parseErrorResponse = async (response) => {
  let payload = null;
  try {
    payload = await response.json();
  } catch (error) {
    payload = null;
  }

  const error = new Error(payload?.error || payload?.message || response.statusText);
  error.status = response.status;
  error.reason = payload?.reason;
  error.code = payload?.error || payload?.code;
  error.payload = payload;
  return error;
};

export const clearStoredCsrfToken = () => {
  persistCsrfToken(null);
};

export const fetchCsrfToken = async () => {
  const response = await fetch('/auth/csrf-token', {
    method: 'GET',
    credentials: 'include'
  });

  if (!response.ok) {
    throw await parseErrorResponse(response);
  }

  const data = await response.json();
  if (data?.csrfToken) {
    persistCsrfToken(data.csrfToken);
  }
  return data?.csrfToken;
};

const ensureCsrfToken = async () => {
  if (!csrfToken) {
    await fetchCsrfToken();
  }
  return csrfToken;
};

export const adminFetch = async (url, options = {}) => {
  const method = (options.method || 'GET').toUpperCase();
  const headers = new Headers(options.headers || {});
  const needsCsrf = options.requireCsrf !== false && !SAFE_METHODS.has(method);

  if (needsCsrf) {
    const token = await ensureCsrfToken();
    if (token) {
      headers.set('X-CSRF-Token', token);
    }
  } else if (csrfToken) {
    headers.set('X-CSRF-Token', csrfToken);
  }

  const response = await fetch(url, {
    ...options,
    method,
    credentials: 'include',
    headers
  });

  if (response.ok) {
    return response;
  }

  // Attempt to refresh CSRF token if it might be invalid
  if (response.status === 403 && needsCsrf) {
    try {
      await fetchCsrfToken();
    } catch (error) {
      // ignore, original error will be thrown below
    }
  }

  throw await parseErrorResponse(response);
};

export const adminGet = async (url) => {
  const response = await adminFetch(url, { method: 'GET', requireCsrf: false });
  return response.json();
};

export const adminRequest = async (url, method, body = null) => {
  const options = {
    method,
    headers: {
      'Content-Type': 'application/json'
    }
  };

  if (body) {
    options.body = JSON.stringify(body);
  }

  const response = await adminFetch(url, options);
  return response;
};

export const adminDownload = async (url) => {
  const response = await adminFetch(url, { method: 'GET', requireCsrf: false });
  return response.blob();
};

export const getSetupStatus = async () => {
  const response = await fetch('/auth/setup/status', {
    method: 'GET',
    credentials: 'include'
  });

  if (!response.ok) {
    throw await parseErrorResponse(response);
  }

  return response.json();
};

export const createInitialAdmin = async ({ username, password }) => {
  const response = await fetch('/auth/setup/initial-admin', {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });

  if (!response.ok) {
    throw await parseErrorResponse(response);
  }

  const data = await response.json();
  if (data?.csrfToken) {
    persistCsrfToken(data.csrfToken);
  }
  return data;
};

export const login = async ({ username, password }) => {
  const response = await fetch('/auth/login', {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });

  if (!response.ok) {
    throw await parseErrorResponse(response);
  }

  const data = await response.json();
  if (data?.csrfToken) {
    persistCsrfToken(data.csrfToken);
  }
  return data;
};

export const logout = async () => {
  await fetch('/auth/logout', {
    method: 'POST',
    credentials: 'include'
  });
  clearStoredCsrfToken();
};

export const changePassword = async ({ currentPassword, newPassword }) => {
  const response = await adminFetch('/auth/change-password', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ currentPassword, newPassword })
  });

  return response.json();
};
