<?php

namespace App\Http\Middleware;

use Carbon\Carbon;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;

class CypherMiddleware
{
    private string $salt = "Test";
    private string $prefix = '#.';
    private string $suffix = '@meta';

    public function __construct()
    {
        $this->salt = env('CYPHER_SALT', 'Test');
        $this->prefix = env('CYPHER_PREFIX', '#.');
        $this->suffix = env('CYPHER_SUFFIX', '@meta');
    }

    /**
     * @param Request $request
     * @param Closure $next
     * @return Response
     */
    public function handle(Request $request, Closure $next): Response
    {
        $cypherKey = $this->getKeySource($request);
        $salt = $this->salt;

        if (empty($cypherKey) || !$this->checkCypherKey($salt, $cypherKey)) {
            Log::error('Cypher key validation failed', [
                'ip' => $request->ip(),
                'provided_key' => $cypherKey,
            ]);
            abort(403, 'Invalid cypherKey');
        }

        return $next($request);
    }


    /**
     * @param Request $request
     * @return string
     */
    private function getKeySource(Request $request): string
    {
        return strval(
            $request->header('cypherKey') ??
            $request->input('cypherKey') ??
            $request->query('cypherKey') ??
            ''
        );
    }

    /**
     * @param string $salt
     * @param string $cypherKey
     * @return bool
     */
    private function checkCypherKey(string $salt, string $cypherKey): bool
    {
        // Generate keys for current time and ±5 minutes
        for ($i = -5; $i <= 5; $i++) {
            $timestamp = Carbon::now('UTC')
                ->addMinutes($i)
                ->startOfMinute()
                ->timestamp;

            $generatedKey = $this->generateKeyForTimestamp($salt, $timestamp);
            if (env('CYPHER_DEBUG', false)) {
                $log = "minutes ({$i}) : " . $generatedKey . " - " . $timestamp;
                echo "$log<br/> ====================== <br> ======================= <br>";
                Log::error($log);
            }

            if (hash_equals($generatedKey, $cypherKey)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param string $salt
     * @param int $timestamp
     * @return string
     */
    private function generateKeyForTimestamp(string $salt, int $timestamp): string
    {
        $stringResult = $this->stringResult($salt, $timestamp);

        return $this->transformString($stringResult, $salt);
    }

    /**
     * @param string $salt
     * @param int $timestamp
     * @return string
     */
    private function stringResult(string $salt, int $timestamp): string
    {
        $shuffle = $this->consistentShuffle($salt);
        $input = $this->prefix . $shuffle . $this->suffix . $timestamp;
        if (env('CYPHER_DEBUG', false)) {
            echo $input . '<br/>';
        }

        return $input;
    }

    /**
     * @param string $str
     * @return string
     */
    private function consistentShuffle(string $str): string
    {
        if (empty($str)) {
            return $str;
        }

        return strrev($str);
    }

    /**
     * @param string $input
     * @param string $salt
     * @return string
     */
    private function transformString(string $input, string $salt): string
    {
        $allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        $hash = $this->multiHash($input, 5, $salt);
        $transformed = "";
        $desiredLength = 36;
        $rounds = 3;

        for ($round = 0; $round < $rounds; $round++) {
            $roundHash = $this->multiHash($hash . $round, 3, $salt);

            for ($i = 0; strlen($transformed) < $desiredLength; $i++) {
                $segment = substr($roundHash, $i * 3, 3);
                $numericValue = hexdec($segment);
                $index = ($numericValue + $i + $round) % strlen($allowedChars);
                $transformed .= $allowedChars[$index];

                if (($i * 3 + 3) >= strlen($roundHash)) {
                    $roundHash = $this->multiHash($roundHash, 2, $salt);
                    $i = -1;
                }
            }

            if (strlen($transformed) >= $desiredLength) {
                break;
            }
        }

        return substr($transformed, 0, $desiredLength);
    }

    /**
     * @param string $input
     * @param int $iterations
     * @param string $salt
     * @return string
     */
    private function multiHash(string $input, int $iterations, string $salt): string
    {
        $hash = $input;
        $shuffle = $this->consistentShuffle($salt);

        for ($i = 0; $i < $iterations; $i++) {
            $hash = hash('sha256', $hash . strrev($shuffle) . $i);
            $hash = substr($hash, 0, 32); // Ensure consistent length
        }

        return $hash;
    }
}
