<?php

class Token extends BaseCode { //미니 접속유지용 토큰

  private const Ezconst = "kuls_Auth_key"; //md5용 Hash (HashCode 생성용)
  private const RdByte = 20; //랜덤 처리용 byte (Unique 발생용)
  private static $Config = Array( //기본설정
    "valid_time" => 3600, //토큰 유효시간 - 1시간
    "expire_time" => 3600 * 24 * 3, //토큰 재발행 유효시간 3일. 이후 재로그인
    "logging_time" => 3600 * 24 * 31 * 3, //로그보관 시간 3달 (현재미사용 - 나중에 로그쌓이는것 보고 사용여부 결정)
  );
  private const SpareTime = 120;

  public function __construct( $cate_table="" ){
    parent::__construct();

    $this->DB->tableExists(TB_token_log);
  }


  /**
   * putToken => 토큰 저장
   * @param string $member_id : 로그인된 사용자 아이디
   * @param string $auth : 인증코드. 각각의 로그인의 Unique세션으로 사용되며 Default는 요청마다 랜덤으로 새로 생성되지만, 인증키를 사용자 아이디로 고정으로 지정하면 중복접속 방지용으로 사용가능(Single Sign On)
   * @param array $config : 기본설정 대체환경
   */
  public function putToken( $member_id, $auth="", $config=Array() ){ //$auth 를 ID로 잡으면 Single Sign On, 그냥두면 Multi login
    $member_id = trim($member_id);
    if( !$member_id ) return false;

    if( defined('_SINGLE_LOGIN_') && _SINGLE_LOGIN_ ){ //Single Sign On 적용
      $auth = $member_id;
    }else{
      $auth = $auth ?: bin2hex(openssl_random_pseudo_bytes(self::RdByte)); //인증키 랜덤생성
    }
    $token = md5(self::Ezconst . $auth . uniqid()); //토큰생성
    $config = $config + self::$Config;

    $valid_time = time() + $config['valid_time'];
    $exprie_time = time() + $config['expire_time'];

    $this->retireToken($auth);

    //$sql = "INSERT INTO ". TB_token_log ." (member_id, auth, token, valid_time, expire_time, active, ip) VALUES (?, ?, ?, ?, ?, ?, ?)";
    $sqlrow = Array(
      "member_id" => $member_id,
      "auth" => $auth,
      "token" => $token,
      "valid_time" => $valid_time,
      "expire_time" => $exprie_time,
      "Active" => "Y",
      "ip" => get_ip()
    );
    $rst = $this->DB->insert(TB_token_log, $sqlrow);
    if( $rst ){
      return $token;
    }
    return false;
  }




  /**
   * retireToken => 토큰 무효화. 비정상 접근시 사용
   * @param string $auth : 인증코드. 해당 인증코드로 생성된 모든 토큰 무효화.
   */
  public function retireToken( $auth ){//모든 $auth 비활성화 처리
    //$sql = "UPDATE TB_token_log SET Active = 'N' WHERE Auth = ?";
    $sqlrow = Array(
      "active" => "N",
    );
    $where = new DB_where("and");
    $where->add("auth = ?", $auth);

    $wqry = $where->get_where();
    $whereqry = $wqry['qry'];
    $whererow = $wqry['val'];

    return $this->DB->update(TB_token_log, $sqlrow, $whereqry, $whererow);
  }



  /**
   * getFromToken => DB에서 토큰 관련정보 획득
   * @param string $token : 사용자가 제출한 토큰.
   */
  public function getFromToken( $token ){//Token 정보 Load
    //$sql = "SELECT USER_ID, Auth, Valid_Time, Expire_Time, Active FROM TB_token_log WHERE Token = ?";
    $where = new DB_where("and");
    $where->add("token = ?", $token);

    $wqry = $where->get_where();
    $whereqry = $wqry['qry'];
    $whererow = $wqry['val'];

    $this->DB->query_record = false; //query log 기록 금지
    $sql = "select member_id, auth, valid_time, expire_time, active from ". TB_token_log ." where {$whereqry}";
    $row = $this->DB->queryFirstRow($sql, $whererow );
    $this->DB->query_record = true; //query log 기록

    return $row;
  }



  /**
   * getLastToken => DB에서 Auth 관련 최종정보 획득
   * @param string $auth : 요청 인증.
   */
  public function getLastAuth( $auth ){//Token 정보 Load
    //$sql = "SELECT USER_ID, Auth, Valid_Time, Expire_Time, Active FROM TB_token_log WHERE Token = ?";
    $where = new DB_where("and");
    $where->add("auth = ?", $auth);
    $where->add("active = ?", "Y");

    $wqry = $where->get_where();
    $whereqry = $wqry['qry'];
    $whererow = $wqry['val'];

    $this->DB->query_record = false; //query log 기록 금지
    $sql = "select top 1 member_id, auth, valid_time, expire_time, active from ". TB_token_log ." where {$whereqry} order by valid_time desc";
    $row = $this->DB->queryFirstRow($sql, $whererow );
    $this->DB->query_record = true; //query log 기록

    return $row;
  }




  /**
   * statusToken =>  Token의 현재상태 판단
   * @param array|string $token_info : 토큰 또는 토큰 정보
   * @return string
   * Y: 인증성공
   * N: 인증실패
   * E: 연장필요 새로운 Token 발행
   * F: 의심되는 접근. 전체 인증 Token 비활성화
   */
  public function statusToken( $token_info ){//Token 상태  (Y:인증성공)
    if( !is_array($token_info) ) $token_info = $this->getFromToken($token_info);

    if( !$token_info['auth'] ) return 'N';

    $now = time();

    switch( true ){
      case ( $token_info['expire_time'] < $now ) : //세션만료
        return 'N'; //인증실패
      case ( $token_info['valid_time'] >= $now ) : //유효시간 이내
        if( trim($token_info['active']) != "Y" ) return "F"; //인증 비활성 필요
        return 'Y'; //인증성공
      case ( $token_info['expire_time'] >= $now ) :
        if( trim($token_info['active']) != "Y" ){
          if( ($token_info['expire_time'] + self::SpareTime) >= $now ){
            return 'Y'; //동시 신호용 인증성공 $spare_time 초간 인정
          }else{
            return "F"; //인증 비활성 필요
          }
        }
        return 'E'; //연장요청
    }

    return 'N';
  }


  public function chkToken( $token ){

    //$fp = fopen(_UP_PATH_."/token_2t.txt", "a+");

    //$fptxt  = "=======================".PHP_EOL;
    //$fptxt .= "NOW=> ".time().PHP_EOL;
    //$fptxt .= "REQ=> ".print_r($_REQUEST,true).PHP_EOL;

    $token_info = $this->getFromToken( $token );
    $token_stauts = $this->statusToken($token_info);
    //$fptxt .= "STATUS=> ".$token_stauts.PHP_EOL;
    switch( $token_stauts ){
      case "E" : //토큰 연장
        $chk_token = $this->getLastAuth($token_info['auth']);
        if( $token_info['token'] == $chk_token['token'] ){
          $new_token = $this->putToken($token_info['member_id'], $token_info['auth']);
        }
      case "Y" : //토큰 유효
        $_SESSION['auth_mem_id'] = $token_info['member_id'];
        break;

        // 토큰 연장처리 되어 새로운 토큰($new_token) 이 발행되었는데도 갱신하지 않고,
        // 예전 토큰 그대로 사용하면 중복 인증요청으로 무효토큰 처리되니
        // 새로운 토큰이 발행되면 다음 요청시엔 꼭 새로운 토큰으로 교체하는 과정이 필요.
        // APP 에서 요청할때 응답이 필요한 요청이외의 단순 요청부분까지 응답을 필수 확인하여 $new_token 값이 있는지 점검후 교체하는 로직 필수!!

      case "F" : //토큰 비활성
        $this->retireToken( $token_info['auth'] );
      case "N" : //토큰 무효
        unset($_SESSION['auth_mem_id']);
        break;
    }
    //fputs($fp, $fptxt);
    //fclose($fp);
    return $new_token;
  }


  /*
  public static function getTokenLogin( $token, $config=Array() ){//Token 처리
    $token_info = self::getToken($token);
    $status = self::stateToken($token_info);

    switch( $status ){
      case "Y" :
        $retval = $member;
        break;
      case "N" :
        $retval = false;
      break;
      case "E" :
        $token = self::putToken($token_info['member_id'], $token_info['auth'], $config);
        $retval = $token;
      break;
      case "F" :
        self::retireToken( $token_info['auth'] );
        $retval = false;
      break;
    }
  }
  */
}