<?php

namespace App\Services;

use App\Models\EntryFile;
use App\Models\LogXfUser;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class FileServersService {

    public const FAVORITE_SERVER_TIME = 3600; // In seconds.
    private const ZEUS_TOKEN_EXPIRATION = 600; // In seconds.

    private string $apiKey;
    private array $servers;

    public function __construct()
    {
        $this->apiKey = config('fileservers.secret_key');
        $this->servers = config('fileservers.servers');
    }

    public function generateZeusToken( int $user_id, string $to, string $action ){

        $info = [
            'user_id' => $user_id,
            'to' => $to,
            'action' => $action,
            'generated_at' => time(),
            'expires_at' => time() + self::ZEUS_TOKEN_EXPIRATION,
            'romhackplaza' => bin2hex(random_bytes(16)),
        ];

        $json = json_encode( $info );
        $sig = hash_hmac( 'sha256', $json, $this->apiKey );

        $end = base64_encode( $json ) . "|" . $sig;
        return $end;
    }

    public function getRandomServerKey(): string
    {
        $keys = array_keys($this->servers);
        return $keys[array_rand($keys)];
    }

    public function getEntryFileServerKey( EntryFile $file ): string
    {
        $serverKey = null;

        if( $file->favorite_server !== null && $file->favorite_at !== null ) {
            if( time() < $file->favorite_at + static::FAVORITE_SERVER_TIME ) {
                $serverKey = $file->favorite_server;
            }
        }
        if( $serverKey === null || !isset( $this->servers[$serverKey] ) ) {
            $serverKey = $this->getRandomServerKey();
        }

        return $serverKey;
    }

    /**
     * Download URL requires 'filepath' and 'filename'.
     *
     * @param EntryFile $file
     * @return string
     */
    public function getDownloadFileUrl( EntryFile $file, bool $countDownload = true ): string
    {
        $serverKey = $this->getEntryFileServerKey( $file );
        $url = $this->servers[$serverKey]['download'] ?? "#";
        if( $url === "#" )
            return $url;

        $args = [ 'filename' => $file->filename, 'filepath' => $file->filepath ];
        if( !$countDownload )
            $args['count_download'] = false;

        return $url . "&" . http_build_query( $args );

    }


    public function getArchiveExplorerUrl( EntryFile $file ): ?string
    {
        if( !Str::endsWith( $file->filename, ['zip', 'rar', '7z'] ) )
            return null;

        $serverKey = $this->getEntryFileServerKey( $file );
        $url = $this->servers[$serverKey]['file_explorer'] ?? "#";

        if( $url === "#" )
            return null;

        $args = [ 'filename' => $file->filename, 'filepath' => $file->filepath,
            'zeus' => $this->generateZeusToken(\Auth::user()->user_id ?? 0, $this->servers[$serverKey]['base_url'], 'Fileexplorer')
        ];

        return $url . "&" . http_build_query( $args );
    }

    /**
     * @throws ConnectionException
     */
    public function uploadChunk(
        UploadedFile $chunk,
        string $fileUUID,
        int $currentChunk,
        int $totalChunks,
        string $filename,
        string $type
    ){

        // Define or get favorite server.
        if( $currentChunk === 0 ){
            $serverKey = $this->getRandomServerKey();
            Cache::put("favorite_server_{$fileUUID}", $serverKey, now()->addHours(2) );
        } else {
            $serverKey = Cache::get( "favorite_server_{$fileUUID}" );
            abort_if( !$serverKey, 422, "File upload expired, please retry." );
        }

        $server = $this->servers[$serverKey];
        $filepath = $type . '/' . $fileUUID;

        $response = Http::withHeaders([])
                     ->attach( 'file', file_get_contents( $chunk->getRealPath() ), $fileUUID )
                     ->post( $server['upload_chunk'], [
                            'filepath' => $filepath,
                            'filename' => $filename,
                            'current_chunk' => $currentChunk,
                            'total_chunks' => $totalChunks,
                            'zeus' => $this->generateZeusToken( \Auth::user()->user_id, $server['base_url'], "Uploadchunk" ),
                     ]);

        if (!$response->successful()) {
            throw new \RuntimeException( $response->body() );
        }

        $data = $response->json();

        if( isset( $data['file'] ) && $data['file'] !== false ){
            Cache::forget( "favorite_server_{$fileUUID}" );
        }
        $data['favorite_server'] = $serverKey;
        $data['file_uuid'] = $fileUUID;
        $data['file_path'] = $filepath;
        return $data;
    }

    public function deleteFile(
        string $filePath,
        string $fileName,
        int $userId
    ): bool {

        foreach( $this->servers as $serverKey => $server ){

            $token = $this->generateZeusToken( $userId, $server['base_url'], "Deletefile" );

            $response = Http::asForm()->withHeaders([])
                ->post( $server['delete_file'], [
                   'filepath' => $filePath,
                   'filename' => $fileName,
                   'zeus' => $token,
                ]);

            if (!$response->successful()) {
                throw new \RuntimeException( $response->body() );
            }

            $data = $response->json();
            if( isset( $data['status'] ) && $data['status'] === 'deleted' ){
                continue;
            } else {
                return false;
            }
        }

        activity('entry-file')
            ->causedBy(LogXfUser::find($userId))
            ->withProperties([
               'filepath' => $filePath,
               'filename' => $fileName,
            ])
            ->event('file_deletion')
            ->log('File deleted');

        return true;
    }

}
