<?php

namespace EesyPHP\Auth;

use EesyPHP\App;
use EesyPHP\Auth;
use EesyPHP\Log;
use EesyPHP\Url;

use phpCAS;

class Cas extends Method {

  /**
   * Fake authenticated user login
   * @var string|null
   */
  private static $fake_authenticated_user = null;

  /**
   * Initialize
   * @return boolean
   */
  public static function init() {
    // In phpstan context, do not initialize
    if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__'))  // @phpstan-ignore-line
      return true;
    // Set config default values
    App :: set_default(
      'auth.cas',
      array(
        'host' => null,
        'port' => 443,
        'context' => '/idp/cas',
        'version' => '2.0',
        'logout' => true,
        'fake_authenticated_user' => null,
        'debug_log_file' => null,
        'ca_cert_certificate_path' => null,
        'user_attributes' => array(
          'login' => array(
            'cas_name' => 'uid',
            'type' => 'string',
            'multivalued' => false,
            'default' => null,
          ),
          'mail' => array(
            'type' => 'string',
            'multivalued' => false,
            'default' => null,
          ),
          'name' => array(
            'cas_name' => 'displayName',
            'alt_cas_name' => 'cn',
            'type' => 'string',
            'multivalued' => false,
            'default' => null,
          ),
        ),
      )
    );
    self :: $fake_authenticated_user = App :: get(
      'auth.cas.fake_authenticated_user', null, 'string');
    if (self :: $fake_authenticated_user) return true;

    if (App::get('auth.cas.debug_log_file'))
        phpCAS::setDebug(App::get('auth.cas.debug_log_file'));

    if (!App::get('auth.cas.host')) {
        Log :: error('CAS host not configured. Check your configuration!');
        return false;
    }

    $cas_version = App :: get('auth.cas.version', null, 'string');
    $supported_cas_versions = phpCAS::getSupportedProtocols();
    if (!array_key_exists($cas_version, $supported_cas_versions)) {
        Log :: error(
            'Unsupported CAS version (%s). Check your configuration!',
            $cas_version
        );
        return false;
    }

    // Init phpCAS client
    phpCAS::client(
        $cas_version,
        App :: get('auth.cas.host'),
        App :: get('auth.cas.port', null, 'int'),
        App :: get('auth.cas.context', null, 'string'),
        Url :: get_absolute_url("/")
    );
    if (App :: get('auth.cas.ca_cert_certificate_path'))
        phpCAS::setCasServerCACert(App :: get('auth.cas.ca_cert_certificate_path'));
    else
        phpCAS::setNoCasServerValidation();
    Url :: add_url_handler(
      '#^login/cas_callback$#', array('EesyPHP\\Auth\\Cas', 'handle_cas_callback'), null, false);
    return true;
  }

  /**
   * Compute CAS callback URL
   * @return string
   */
  private static function get_cas_callback_url() {
    return Url :: get_absolute_url(
      'login/cas_callback?next='.(
        isset($_REQUEST['next'])?
        $_REQUEST['next']:
        urlencode(Url :: get_current_url())
      )
    );
  }

  /**
   * Log user
   * @param bool $force Force user authentication
   * @return \EesyPHP\Auth\User|null
   */
  public static function login($force=false) {
    if (self :: $fake_authenticated_user) {
      $user = Auth :: get_user(self :: $fake_authenticated_user);
    }
    else {
      if (!phpCAS :: isAuthenticated() && $force) {
        $_SESSION['cas_callback_url'] = self :: get_cas_callback_url();
        phpCAS :: setFixedServiceURL($_SESSION['cas_callback_url']);
        phpCAS :: forceAuthentication();
      }
      $user = (
        phpCAS :: isAuthenticated()?
        Auth :: get_user(phpCAS :: getUser()):
        null
      );
    }
    if ($force && !$user)
      Log :: fatal('Fail to authenticate you');
    return $user;
  }

  /**
   * Logout
   * @return void
   */
  public static function logout() {
    if (App :: get('auth.cas.logout', null, 'bool') && !self :: $fake_authenticated_user) {
      if (App :: get('auth.cas.logout_url'))
        Url :: redirect(App :: get('auth.cas.logout_url'));
      phpCAS::logout();
    }
    else {
      session_unset();
      session_destroy();
    }
  }

  /**
   * The CAS callback view
   * @param \EesyPHP\UrlRequest $request
   * @return never
   */
  public static function handle_cas_callback($request) {
    if (isset($_SESSION['cas_callback_url'])) {
      phpCAS :: setFixedServiceURL($_SESSION['cas_callback_url']);
      unset($_SESSION['cas_callback_url']);
    }
    $user = Auth :: login(false, 'Cas');
    if ($user)
      Url :: redirect(isset($_REQUEST['next'])?urldecode($_REQUEST['next']):null);
    Log :: fatal('No CAS ticket or fail to authenticate you');
  }
}
