<?php

/**
 * Tokens for authorizing access to ecobee accounts.
 *
 * @author Jon Ziebell
 */
class ecobee_token extends cora\crud {

  /**
   * Create an ecobee token. Does the normal CRUD create and also extracts the
   * ecobee_account_id from the token and attaches it to the user. A user only
   * has one ecobee_token row so this really only runs once per user.
   *
   * @param array $attributes
   *
   * @return int
   */
  public function create($attributes) {
    $this->api(
      'user',
      'update',
      [
        'attributes' => [
          'user_id' => $this->session->get_user_id(),
          'ecobee_account_id' => $this->get_ecobee_account_id($attributes)
        ]
      ]
    );

    return parent::create($attributes);
  }

  /**
   * This should be called when connecting a new user. Get the access/refresh
   * tokens, then attach them to a brand new anonymous user.
   *
   * @param string $code The code from ecobee used to obtain the
   * access/refresh tokens.
   *
   * @return array The access/refresh tokens.
   */
  public function obtain($code) {
    // Obtain the access and refresh tokens from the authorization code.
    $response = $this->api(
      'ecobee',
      'ecobee_api',
      [
        'method' => 'POST',
        'endpoint' => 'token',
        'arguments' => [
          'grant_type' => 'authorization_code',
          'code' => $code,
          'redirect_uri' => $this->setting->get('ecobee_redirect_uri')
        ]
      ]
    );

    // Make sure we got the expected result.
    if (
      isset($response['access_token']) === false ||
      isset($response['refresh_token']) === false
    ) {
      throw new cora\exception('Could not get first ecobee token.', 10000);
    }

    return [
      'access_token' => $response['access_token'],
      'refresh_token' => $response['refresh_token'],
      'ecobee_account_id' => $this->get_ecobee_account_id($response),
      'timestamp' => date('Y-m-d H:i:s'),
      'deleted' => 0
    ];
  }

  /**
   * Get an ecobee_account_id from the ecobee JWT. Check a bunch of stuff to
   * make sure it's valid.
   *
   * @param ecobee_token $ecobee_token The ecobee_token.
   *
   * @return string The ecobee_account_id.
   */
  public function get_ecobee_account_id($ecobee_token) {
    $parts = explode('.', $ecobee_token['access_token']);
    if(count($parts) !== 3) {
      return null;
    }

    $payload = $parts[1];
    $payload = str_replace(['_', '-'], ['/', '+'], $payload);

    $json = base64_decode($payload);

    if($json === false) {
      return null;
    }

    $object = json_decode($json, true);
    if($object === null) {
      return null;
    }

    if(isset($object['sub']) === false) {
      return null;
    }

    $sub_parts = explode('|', $object['sub']);
    if(count($sub_parts) !== 2) {
      return null;
    }

    $ecobee_account_id = $sub_parts[1];

    if(strlen($ecobee_account_id) !== 36) {
      return null;
    }

    return $ecobee_account_id;
  }

  /**
   * Get some new tokens. A database lock is obtained prior to getting a token
   * so that no other API call can attempt to get a token at the same time.
   * This way if two API calls fire off to ecobee at the same time, then
   * return at the same time, then call token->refresh() at the same time,
   * only one can run and actually refresh at a time. If the transactionless
   * one runs after that's fine as it will look up the token prior to
   * refreshing.
   *
   * @return array The new token.
   */
  public function refresh() {
    $database = cora\database::get_transactionless_instance();

    $lock_name = 'ecobee_token->refresh(' . $this->session->get_user_id() . ')';
    $database->get_lock($lock_name, 3);

    $ecobee_tokens = $database->read(
      'ecobee_token',
      [
        'user_id' => $this->session->get_user_id(),
        'deleted' => false
      ]
    );

    if(count($ecobee_tokens) === 0) {
      throw new cora\exception('Could not refresh ecobee token; no token found.', 10001);
    }
    $ecobee_token = $ecobee_tokens[0];

    $response = $this->api(
      'ecobee',
      'ecobee_api',
      [
        'method' => 'POST',
        'endpoint' => 'token',
        'arguments' => [
          'ecobee_type' => 'jwt',
          'grant_type' => 'refresh_token',
          'refresh_token' => $ecobee_token['refresh_token']
        ]
      ]
    );

    if (
      isset($response['access_token']) === false ||
      isset($response['refresh_token']) === false
    ) {
      $this->delete($ecobee_token['ecobee_token_id']);
      $database->release_lock($lock_name);
      throw new cora\exception('Could not refresh ecobee token; ecobee returned no token.', 10002, true, null, false);
    }

    $ecobee_token = $database->update(
      'ecobee_token',
      [
        'ecobee_token_id' => $ecobee_token['ecobee_token_id'],
        'access_token' => $response['access_token'],
        'refresh_token' => $response['refresh_token'],
        'timestamp' => date('Y-m-d H:i:s')
      ]
    );

    $this->api(
      'user',
      'update',
      [
        'attributes' => [
          'user_id' => $this->session->get_user_id(),
          'ecobee_account_id' => $this->get_ecobee_account_id($ecobee_token)
        ]
      ]
    );

    $database->release_lock($lock_name);

    return $ecobee_token;
  }

  /**
   * Delete an ecobee token and log the user out. Make sure to delete the
   * token prior to logging out so the right permissions are present.
   *
   * @param int $id
   *
   * @return int
   */
  public function delete($id) {
    $return = parent::delete($id);
    $this->api('user', 'log_out');
    return $return;
  }

}
