const MOCK_ROUTES = {};

import { user, cache_reset } from "./stores.js";
import { log } from "$/client/logging.js";
import Validator from 'Validator';

export const validate = (form, extra) => {
  if(form._rules) {
    let validation = Validator.make(form, form._rules);
    form._valid = validation.passes();

    if(extra) extra(form);
    form._errors = validation.getErrors();

    return form;
  } else {
    return form;
  }
}

export const can_submit = (form) => {
  return form._rules === undefined || form._valid;
}

export const clean_form = (form, extras=[]) => {
  form._errors = {}; // errors is accessed so needs to exist
  delete form._valid;
  delete form._rules;
  for(let field of extras) delete form[field];
}

// use these when you do your own fetch
const fetch_opts = { credentials: 'same-origin',};

export const mock = (config) => {
  for(let [route, value] of Object.entries(config)) {
    MOCK_ROUTES[route] = value;
  }
}

export const raw_mock = (url, raw_method, body, unauthed_action) => {
  let error;
  const method = raw_method.toLowerCase();
  const config = MOCK_ROUTES[url];
  const data = config[method];

  if(data === undefined) {
    error = `Mock ${url}:${method} is not in your mocks list.  Did you forget to add it to the api.mock call?`;
  } else if(data.length !== 2) {
    error = `Mock ${url}:${method} does not have the correct config. Must be [status (int), {data}], like [200, {message: 'OK'}].`
  }

  if(error) {
    log.error(error, MOCK_ROUTES);
    return [500, {message: error}];
  } else {
    // this is the same as res.status below
    if(unauthed_action && (data[0] === 401 || data[0] === 403)) unauthed_action();
    return data;
  }
}

const parse_url_because_js_is_stupid = (url) => {
  try {
    // javascript is dumb as hell and thinks a typical /this/that is not a URL
    return new URL(url);
  } catch(error) {
    // so fake it for the mock by tacking on ... localhost then ignoring it
    return new URL(`http://localhost${url}`);
  }
}

export const raw = async (url, method, body, unauthed_action) => {
  const parsed = parse_url_because_js_is_stupid(url);

  // if there is a rules and valid is exactly false we should just run
  // the validation here and avoid the request
  if(body && body._rules && body._valid === false) {
    const res = validate(body);

    if(!can_submit(res)) {
      log.debug("Form invalid, won't HTTP submit.");
      return [400, res];
    } else {
      clean_form(body);
    }
  }

  if(parsed.pathname in MOCK_ROUTES) {
    return raw_mock(parsed.pathname, method, body);
  } else {
    let options = {
      method,
      credentials: 'same-origin',
      headers: {
        'Content-Type': 'application/json',
      },
    }

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

    /* this handy little gem exists because fetch likes to go:
     * "the string did not match the expected pattern”
     * when the response body is not json...or at least that's
     * what I think it means.  Now I get the text, and use it
     * for logging.
     */
    let res = await fetch(url, options);
    let text = await res.text();

    try {
      if(unauthed_action && (res.status === 401 || res.status === 403)) unauthed_action();

      return [res.status, JSON.parse(text)];
    } catch(error) {
      log.error(error, "Failed to parse reply body as JSON. Text is:", text, "error", error, "URL", url);
      return [500, {"message": "Exception processing request. See log.debug."}];
    }
  }
}

export const get = async (url, data, unauthed_action) => {
  const params = new URLSearchParams(data || {});
  const param_url = `${url}?${params.toString()}`;
  return await raw(param_url, 'GET', undefined, unauthed_action);
}

export const post = async (url, data, unauthed_action) => {
  return await raw(url, 'POST', data, unauthed_action);
}

export const put = async (url, data, unauthed_action) => {
  return await raw(url, 'PUT', data, unauthed_action);
}

export const del = async (url, unauthed_action) => {
  return await raw(url, 'DELETE', undefined, unauthed_action);
}

export const options = async (url, unauthed_action) => {
  return await raw(url, 'OPTIONS', undefined, unauthed_action);
}

export const logout_user = async () => {
  user.update(() => {
    return {authenticated: undefined}
  });

  // really only a problem for multiple people
  // using the same browser, but clear it out
  cache_reset();

  let [status, data] = await get('/api/logout');
  if(status !== 200) log.error("Invalid status from logout", status, data);

  window.location.replace("/client/#/login");
}

export const schema = async (table) => {
  let [status, tables] = await get('/api/admin/schema');

  if(status == 200) {
    for(let t of tables) {
      if(t.name === table) {
        // this exits the function with the schema
        return t._columns;
      }
    }

    // this happens if the table isn't in the schema
    return undefined;
  } else {
    return undefined;
  }
}

export default {
  post, get, put, del, mock, options, fetch_opts,
  logout_user, validate, can_submit, clean_form, schema
}
