import to from 'await-to-js';
import { trimTrailingSlash } from './utils/common';

export enum AutoDiscoveryAction {
  PROMPT = 'PROMPT',
  IGNORE = 'IGNORE',
  FAIL_PROMPT = 'FAIL_PROMPT',
  FAIL_ERROR = 'FAIL_ERROR',
}

export type AutoDiscoveryError = {
  host: string;
  action: AutoDiscoveryAction;
};

export type AutoDiscoveryInfo = Record<string, unknown> & {
  'm.homeserver': {
    base_url: string;
  };
  'm.identity_server'?: {
    base_url: string;
  };
  // v1.15 stable next-gen-auth (MSC2965) discovery key — emitted by servers that
  // delegate to a Matrix Authentication Service (e.g. mozilla.org). The
  // `org.matrix.msc2965.authentication` key below is the unstable predecessor.
  'm.authentication'?: {
    issuer?: string;
    account?: string;
  };
  'org.matrix.msc2965.authentication'?: {
    account?: string;
    issuer?: string;
  };
  'org.matrix.msc4143.rtc_foci'?: [
    {
      livekit_service_url: string;
      type: 'livekit';
    },
  ];
};

/**
 * Resolve the OIDC issuer (and account-management URL) advertised by a homeserver
 * in its `.well-known/matrix/client`, preferring the v1.15 stable
 * `m.authentication` key over the unstable `org.matrix.msc2965.authentication`.
 * Returns `{}` when the server is not OIDC-native (e.g. matrix.lotusguild.org).
 */
export const getOidcIssuer = (info: AutoDiscoveryInfo): { issuer?: string; account?: string } => {
  const stable = info['m.authentication'];
  if (stable && typeof stable.issuer === 'string') {
    return { issuer: stable.issuer, account: stable.account };
  }
  const unstable = info['org.matrix.msc2965.authentication'];
  if (unstable && typeof unstable.issuer === 'string') {
    return { issuer: unstable.issuer, account: unstable.account };
  }
  return {};
};

export const autoDiscovery = async (
  request: typeof fetch,
  server: string,
): Promise<[AutoDiscoveryError, undefined] | [undefined, AutoDiscoveryInfo]> => {
  const host = /^https?:\/\//.test(server) ? trimTrailingSlash(server) : `https://${server}`;
  const autoDiscoveryUrl = `${host}/.well-known/matrix/client`;

  const [err, response] = await to(request(autoDiscoveryUrl, { method: 'GET' }));

  if (err || response.status === 404) {
    // AutoDiscoveryAction.IGNORE
    // We will use default value for IGNORE action
    return [
      undefined,
      {
        'm.homeserver': {
          base_url: host,
        },
      },
    ];
  }
  if (response.status !== 200) {
    return [
      {
        host,
        action: AutoDiscoveryAction.FAIL_PROMPT,
      },
      undefined,
    ];
  }

  const [contentErr, content] = await to<AutoDiscoveryInfo>(response.json());

  if (contentErr || typeof content !== 'object') {
    return [
      {
        host,
        action: AutoDiscoveryAction.FAIL_PROMPT,
      },
      undefined,
    ];
  }

  const baseUrl = content['m.homeserver']?.base_url;
  if (typeof baseUrl !== 'string') {
    return [
      {
        host,
        action: AutoDiscoveryAction.FAIL_PROMPT,
      },
      undefined,
    ];
  }

  if (/^https?:\/\//.test(baseUrl) === false) {
    return [
      {
        host,
        action: AutoDiscoveryAction.FAIL_ERROR,
      },
      undefined,
    ];
  }

  content['m.homeserver'].base_url = trimTrailingSlash(baseUrl);
  if (content['m.identity_server']) {
    content['m.identity_server'].base_url = trimTrailingSlash(
      content['m.identity_server'].base_url,
    );
  }

  return [undefined, content];
};

export type SpecVersions = {
  versions: string[];
  unstable_features?: Record<string, boolean>;
};
export const specVersions = async (
  request: typeof fetch,
  baseUrl: string,
): Promise<SpecVersions> => {
  const res = await request(`${trimTrailingSlash(baseUrl)}/_matrix/client/versions`);

  const data = (await res.json()) as unknown;

  if (data && typeof data === 'object' && 'versions' in data && Array.isArray(data.versions)) {
    return data as SpecVersions;
  }
  throw new Error('Homeserver URL does not appear to be a valid Matrix homeserver');
};
