<?php

/*
 * This file is part of the overtrue/wechat.
 *
 * (c) overtrue <i@overtrue.me>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */
namespace EasyWeChat\Payment\Notify;

use Closure;
use EasyWeChat\Kernel\Exceptions\Exception;
use EasyWeChat\Kernel\Support;
use EasyWeChat\Kernel\Support\XML;
use EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException;
use Symfony\Component\HttpFoundation\Response;
abstract class Handler
{
    const SUCCESS = 'SUCCESS';
    const FAIL = 'FAIL';
    /**
     * @var \EasyWeChat\Payment\Application
     */
    protected $app;
    /**
     * @var array
     */
    protected $message;
    /**
     * @var string|null
     */
    protected $fail;
    /**
     * @var array
     */
    protected $attributes = [];
    /**
     * Check sign.
     * If failed, throws an exception.
     *
     * @var bool
     */
    protected $check = true;
    /**
     * Respond with sign.
     *
     * @var bool
     */
    protected $sign = false;
    /**
     * @param \EasyWeChat\Payment\Application $app
     */
    public function __construct($app)
    {
        $this->app = $app;
    }
    /**
     * Handle incoming notify.
     *
     * @param \Closure $closure
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public abstract function handle(Closure $closure);
    /**
     * @param $message
     */
    public function fail($message)
    {
        $this->fail = $message;
    }
    /**
     * @param array $attributes
     * @param  $sign
     *
     * @return $this
     */
    public function respondWith(array $attributes, $sign = false)
    {
        $this->attributes = $attributes;
        $this->sign = $sign;
        return $this;
    }
    /**
     * Build xml and return the response to WeChat.
     *
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     */
    public function toResponse()
    {
        $base = ['return_code' => is_null($this->fail) ? static::SUCCESS : static::FAIL, 'return_msg' => $this->fail];
        $attributes = array_merge($base, $this->attributes);
        if ($this->sign) {
            $attributes['sign'] = Support\generate_sign($attributes, $this->app->getKey());
        }
        return new Response(XML::build($attributes));
    }
    /**
     * Return the notify message from request.
     *
     * @return array
     *
     * @throws \EasyWeChat\Kernel\Exceptions\Exception
     */
    public function getMessage()
    {
        if (!empty($this->message)) {
            return $this->message;
        }
        try {
            $message = XML::parse(strval($this->app['request']->getContent()));
        } catch (\Throwable $e) {
            throw new Exception('Invalid request XML: ' . $e->getMessage(), 400);
        }
        if (!is_array($message) || empty($message)) {
            throw new Exception('Invalid request XML.', 400);
        }
        if ($this->check) {
            $this->validate($message);
        }
        return $this->message = $message;
    }
    /**
     * Decrypt message.
     *
     * @param $key
     *
     * @return string|null
     *
     * @throws \EasyWeChat\Kernel\Exceptions\Exception
     */
    public function decryptMessage($key)
    {
        $message = $this->getMessage();
        if (empty($message[$key])) {
            return null;
        }
        return Support\AES::decrypt(base64_decode($message[$key], true), md5($this->app['config']->key), '', OPENSSL_RAW_DATA, 'AES-256-ECB');
    }
    /**
     * Validate the request params.
     *
     * @param array $message
     *
     * @throws \EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     */
    protected function validate(array $message)
    {
        $sign = $message['sign'];
        unset($message['sign']);
        if (Support\generate_sign($message, $this->app->getKey()) !== $sign) {
            throw new InvalidSignException();
        }
    }
    /**
     * @param mixed $result
     */
    protected function strict($result)
    {
        if (true !== $result && is_null($this->fail)) {
            $this->fail(strval($result));
        }
    }
}