import Service from '@ember/service';
import {
  startAuthentication,
  startRegistration,
} from '@simplewebauthn/browser';

export default class AuthService extends Service {
  async startLogin(username: string) {
    // Get login options from your server. Here, we also receive the challenge.
    const response = await fetch('/webauthn/passkey/loginBegin', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username: username }),
    });
    // Check if the login options are ok.
    if (!response.ok) {
      const msg = await response.json();
      throw new Error('Failed to get login options from server: ' + msg);
    }
    // Convert the login options to JSON.
    const options = await response.json();

    // This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello).
    // A new assertionResponse is created. This also means that the challenge has been signed.
    const assertionResponse = await startAuthentication(options.publicKey);

    // Send assertionResponse back to server for verification.
    const verificationResponse = await fetch('/webauthn/passkey/loginFinish', {
      method: 'POST',
      credentials: 'same-origin',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(assertionResponse),
    });

    const msg = await verificationResponse.json();
    if (verificationResponse.ok) {
      return;
    } else {
      throw new Error(
        'Bad response code: ' +
          verificationResponse.status +
          '. Content: ' +
          msg,
      );
    }
  }

  async startRegistration(username: string) {
    // Get registration options from your server. Here, we also receive the challenge.
    const response = await fetch('/webauthn/passkey/registerBegin', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username: username }),
    });

    // Check if the registration options are ok.
    if (!response.ok) {
      const msg = await response.json();
      throw new Error(
        'User already exists or failed to get registration options from server: ' +
          msg,
      );
    }

    // Convert the registration options to JSON.
    const options = await response.json();
    console.log('registration start', options);

    // This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello).
    // A new attestation is created. This also means a new public-private-key pair is created.
    const attestationResponse = await startRegistration(options.publicKey);

    console.log('Attempting to complete registration', attestationResponse);
    // Send attestationResponse back to server for verification and storage.
    const verificationResponse = await fetch(
      '/webauthn/passkey/registerFinish',
      {
        method: 'POST',
        credentials: 'same-origin',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(attestationResponse),
      },
    );

    const msg = await verificationResponse.json();
    if (verificationResponse.ok) {
      return;
    } else {
      throw new Error(
        'Bad response code: ' +
          verificationResponse.status +
          '. Content: ' +
          msg,
      );
    }
  }

  // Check if the client is currently logged in
  // Happens via caling an endpoint that's only available if logged in but doesn't return anything other than "ok"
  async getLoginState(): Promise<boolean> {
    try {
      const response = await fetch('/api/test-auth');
      return response.status >= 200 && response.status < 300;
    } catch (error: any) {
      return false;
    }
  }

  // Check if a given username exist on the server
  // Note: The server enforces this check itself during both registration and login
  // but provides the information too so that frontends can provide a better UX
  async doesUsernameExist(username: string): Promise<boolean> {
    // TODO: Make API call to check if username/handle is already in use
    return true;
  }

  // Check if a given username is allowed to log in.
  // This includes a check for the existence of the username in the first place
  // A username may not log in for various reasons, two of which are the account not being approved yet
  // or the account being barred login from an admin
  // Note: The server enforces this check itself during login. However, it also provides an API endpoint
  // for performing this check to allow frontends to have a better UX
  async canUsernameLogin(username: string): Promise<boolean> {
    // Can't login into a non-existing account
    if (!(await this.doesUsernameExist(username))) return false;
    // TODO: Make API call to check if username is allowed to login
    return true;
  }
}

// Don't remove this declaration: this is what enables TypeScript to resolve
// this service using `Owner.lookup('service:auth')`, as well
// as to check when you pass the service name as an argument to the decorator,
// like `@service('auth') declare altName: AuthService;`.
declare module '@ember/service' {
  interface Registry {
    auth: AuthService;
  }
}
