<?php

namespace Application\Service\Extension\Middleware;

use Application\Service\DB\Db;
use Application\Service\Exceptions\InvalidArgumentException;

class Middleware
{
    private $owner = null;

    private ?array $params = null;

    public const POSITION_BEFORE = 'before';

    public const POSITION_AFTER = 'after';

    protected array $beforeMiddlewareMap = [];

    protected array $afterMiddlewareMap = [];
    protected array $afterTransactionMiddlewareMap = [];    // 需要事务之后处理

    protected $isError = 0;

    public $errors = [];

    public function __construct(&$owner = null,$isError = 0)
    {
        $this->isError = $isError;
        $this->owner = &$owner;
    }

    public function execute(\Closure $function)
    {
        Db::beginTransaction();

        try{

            foreach ($this->beforeMiddlewareMap as $middleware) {
                $middleware(self::POSITION_BEFORE);
            }
            $val = $function();

            foreach ($this->afterMiddlewareMap as $middleware) {
                $middleware(self::POSITION_AFTER, $val);
            }
        }catch (\Exception $e){
            Db::rollBack();

            throw new InvalidArgumentException($e->getMessage());
        }

        Db::commit();

        foreach ($this->afterTransactionMiddlewareMap as $middleware) {
            $middleware(self::POSITION_AFTER, $val);
        }

        return $val;
    }

    public function importexecute(\Closure $function)
    {
        $errors = [];
        Db::beginTransaction();
        foreach ($this->beforeMiddlewareMap as $middleware) {
            try {
                // 执行中间件，并传递当前阶段
                $middleware(self::POSITION_BEFORE);
            } catch (\Exception $e) {
                // 收集错误信息
                $this->errors[] = $e->getMessage();
            }
        }

        $this->errors = array_merge(...$this->errors);

        $val = $function();

        if(!empty($val)) $this->errors = array_merge($this->errors,$val);

        return $this->errors;
    }

    public function beforeMiddleware(array $middlewareMap): Middleware
    {
        foreach ($middlewareMap as $middleware) {
            if (is_string($middleware)) {
                $params = [$middleware];
            } else {
                $params = $middleware;
            }
            $this->beforeMiddlewareMap[] = $this->instanceComponent(...$params);
        }

        return $this;
    }

    /**
     * 后置中间件。事务需要自己再处理一下。
     * @param array $middlewareMap
     * @return $this
     */
    public function afterMiddleware(array $middlewareMap): Middleware
    {
        foreach ($middlewareMap as $middleware) {
            if (is_string($middleware)) {
                $params = [$middleware];
            } else {
                $params = $middleware;
            }
            $this->afterMiddlewareMap[] = $this->instanceComponent(...$params);
        }

        return $this;
    }

    public function afterTransactionMiddleware(array $middlewareMap): Middleware
    {
        foreach ($middlewareMap as $middleware) {
            if (is_string($middleware)) {
                $params = [$middleware];
            } else {
                $params = $middleware;
            }
            $this->afterTransactionMiddlewareMap[] = $this->instanceComponent(...$params);
        }

        return $this;
    }

    private function instanceComponent(string $middleComponent, $params = []): \Closure
    {
        return function($position, &$executeVal = null) use ($middleComponent, $params) {
            $ref = new \ReflectionClass($middleComponent);
            if (!$ref->implementsInterface(MiddlewareComponentInterface::class)) {
                throw new \Exception('middleware component must implements MiddlewareComponentInterface');
            }

            /** @var BaseMiddlewareComponent $class */
            $class = $ref->newInstance($params);
            if ($position === self::POSITION_AFTER) {
                $class->responseValue = &$executeVal;
            }
            $class->owner = &$this->owner;
            $class->params = &$this->params;
            $class->position = $position;
            $result = $class->execute($this->isError);
            if($this->isError == 1 && !empty($result)){
                $this->errors[] = $result;
            }
        };
    }

    /**
     * 设置中间件参数。
     * 方便往中间件传参。
     */
    public function setParams(array $params = []): Middleware
    {
        $this->params = $params;
        return $this;
    }
}