<?php

namespace Buckhill\U2F\Enroll;


use Buckhill\U2F\U2FHelper;
use Buckhill\U2F\Exceptions\InvalidSignatureException;

/**
 * Class Response
 * @package Buckhill\U2F\Enroll
 */
class Response
{
    protected $registration_data;
    protected $challenge;
    protected $version;
    protected $app_id;
    protected $client_data;
    protected $decoded;
    protected $registration;
    protected $client;
    protected $offset = 1;
    protected $parser;

    public function __construct($registration_data, $challenge, $version, $app_id, $client_data)
    {
        $this->registration_data = $registration_data;
        $this->challenge = $challenge;
        $this->version = $version;
        $this->app_id = $app_id;
        $this->client_data = $client_data;

        $this->decode();
    }


    /**
     * @return array
     */
    public function toArray()
    {
        return [
            'pubKey'        => base64_encode($this->getParser()->getPublicKey()),
            'keyHandle'     => U2FHelper::encode($this->getParser()->getKeyHandle()),
            'certificate'   => base64_encode($this->getParser()->getRawCertificate()),
            'appId'         => $this->app_id,
            'counter'       => 0
        ];
    }


    /**
     * @param bool|true $pem
     *
     * @return mixed
     */
    public function getPublicKey($pem = true)
    {
        return $pem ?
            $this->getParser()->getPEMPublicKey()
            :
            $this->getParser()->getPublicKey();
    }


    /**
     * @return mixed
     */
    public function getKeyHandle()
    {
        return $this->getParser()->getKeyHandle();
    }


    /**
     * @param bool|false $raw
     *
     * @return mixed
     */
    public function getAttestationCertificate($raw = false)
    {
        return $raw ?
            $this->getParser()->getRawCertificate()
            :
            $this->getParser()->getPEMEncodedCertificate();
    }


    /**
     * @return RequestParser
     */
    public function getParser()
    {
        if(!$this->parser instanceof RequestParser)
        {
            throw new \DomainException("No instance of RequestParser found. Invoke ::parse() method before retrieving the parser instance.");
        }

        return $this->parser;
    }


    /**
     * @param array $data
     *
     * @return static
     */
    public static function fromArray(array $data)
    {
        static::validateData($data);

        return new static($data['registrationData'], $data['challenge'], $data['version'], $data['appId'], $data['clientData']);
    }


    /**
     * @return $this
     */
    public function parse()
    {
        $this->parser = new RequestParser($this->decoded);

        $this->parser->parse();

        return $this;
    }


    /**
     * @return $this
     * @throws InvalidSignatureException
     */
    public function verifySignature()
    {
        $signature = $this->getParser()->getSignature();

        $dataToVerify  = chr(0);
        $dataToVerify .= hash('sha256', $this->app_id, true);
        $dataToVerify .= hash('sha256', $this->decoded['clientData'], true);
        $dataToVerify .= $this->getParser()->getKeyHandle();
        $dataToVerify .= $this->getParser()->getPublicKey();

        $result = openssl_verify($dataToVerify, $signature, $this->getParser()->getPEMEncodedCertificate(), 'sha256');

        if(!($result === 1))
        {
            throw new InvalidSignatureException("Invalid signature, input can't be verified.");
        }

        return $this;
    }


    /**
     * @param array $data
     */
    protected static function validateData(array $data)
    {
        $required = ['registrationData', 'challenge', 'version', 'appId', 'clientData'];

        foreach($required as $key)
        {
            if(!isset($data[$key]))
            {
                throw new \InvalidArgumentException(sprintf("Required key %s was not specified.", $key));
            }
        }
    }


    /**
     * @return $this
     */
    protected function decode()
    {
        return $this
            ->decodeRegistrationData()
            ->decodeClientData()
        ;
    }


    /**
     * @return $this
     */
    protected function decodeRegistrationData()
    {
        $decoded = U2FHelper::decode($this->registration_data);
        $unpacked = array_values(unpack('C*', $decoded));

        $this->decoded['registrationData'] = $decoded;
        $this->decoded['registration'] = $unpacked;

        return $this;
    }


    /**
     * @return $this
     */
    protected function decodeClientData()
    {
        $decoded = U2FHelper::decode($this->client_data);
        $json = json_decode($decoded, true);

        $this->decoded['clientData'] = $decoded;
        $this->decoded['client'] = $json;

        return $this;
    }
}