<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use Hypervel\Http\Request;
use App\Models\FileList;
use App\Models\FileContent;
use Hypervel\Http\UploadedFile;
use Hypervel\Support\Facades\File;
use Hypervel\Support\Facades\Storage;
use Hypervel\Support\Facades\Auth;
use Hypervel\Support\Facades\DB;
use \PDO;
use Hypervel\Support\Facades\Response;
use Hypervel\Support\Carbon;
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
use App\Http\Controllers\Helpers\Permissions\ProductPermissions;
use App\Enums\UserActions;

class FilesMainController
{

    private static function isLikelyBinary($string, $threshold = 0.3)
    {
        // Null byte check: very likely binary
        if (strpos($string, "\x00") !== false) {
            return true;
        }

        // If string is empty, consider it not binary
        $len = strlen($string);
        if ($len === 0)
            return false;

        // Count non-printable characters
        $nonPrintable = preg_match_all('~[^\x09\x0A\x0D\x20-\x7E]~', $string);

        // If more than $threshold of the content is non-printable, likely binary
        return ($nonPrintable / $len) > $threshold;
    }

    private static function insertFileContentPostgresPDO(array $data)
    {
        $pdo = DB::getPdo();

        $stmt = $pdo->prepare('
        INSERT INTO file_content 
            (hashkey, filehash, titlename, description, size_in_bytes, content, filelocation, created_by, updated_by, details, created_at, updated_at)
        VALUES
            (:hashkey, :filehash, :titlename, :description, :size_in_bytes, :content, :filelocation, :created_by, :updated_by, :details, :created_at, :updated_at)
    ');

        $now = now()->toDateTimeString();

        $stmt->bindParam(':hashkey', $data['hashkey']);
        $stmt->bindParam(':filehash', $data['filehash']);
        $stmt->bindParam(':titlename', $data['titlename']);
        $stmt->bindParam(':description', $data['description']);
        $stmt->bindParam(':size_in_bytes', $data['size_in_bytes']);
        $stmt->bindParam(':content', $data['content'], PDO::PARAM_LOB);
        $stmt->bindParam(':filelocation', $data['filelocation']);
        $stmt->bindParam(':created_by', $data['created_by']);
        $stmt->bindParam(':updated_by', $data['updated_by']);
        $detailsJson = json_encode($data['details']);
        $stmt->bindParam(':details', $detailsJson);
        $stmt->bindParam(':created_at', $now);
        $stmt->bindParam(':updated_at', $now);

        $stmt->execute();

        // Optionally, get the inserted ID and fetch the model
        $id = DB::getPdo()->lastInsertId();

        return FileContent::find($id);
    }

    private static function insertFileContentSql(array $data)
    {
        $now = Carbon::now();

        $data['content'] = base64_encode($data['content']);
        $data['details'] = json_encode($data['details']);
        $data['created_at'] = $now;
        $data['updated_at'] = $now;

        return FileContent::create($data);
    }


    private static function insertFileContent(array $data)
    {
        $driver = DB::connection()->getDriverName();

        if ($driver === 'pgsql') {
            return self::insertFileContentPostgresPDO($data);
        }

        if ($driver === 'mysql') {
            return self::insertFileContentSql($data);
        }

        throw new \RuntimeException("Unsupported database driver: {$driver}");
    }



    /**
     * Upload file content and store metadata in DB.
     *
     * @param \Hypervel\Http\UploadedFile|string $fileData // filepath, filebinaryData, UploadedFileRequest
     * @param string $title
     * @param string|null $description
     * @param string|null $filelocation
     * @param array|null $details
     */
    private static function uploadFileContent(UploadedFile|string $fileData, string $title, ?string $description = null, ?array $details = [])
    {


        if ($fileData instanceof UploadedFile) {
            $fileHash = hash_file('sha256', $fileData->getRealPath());
            $fileSize = $fileData->getSize();
            $filename = $fileHash;
            $fileContent = File::get($fileData->getRealPath());
            $path = $fileData->storeAs('files', $filename);
        } elseif (is_string($fileData) && file_exists($fileData)) {

            $fileHash = hash_file('sha256', $fileData);
            $fileSize = filesize($fileData);
            $fileContent = file_get_contents($fileData);
            $path = Storage::put("files/{$fileHash}", $fileContent);

        } elseif (self::isLikelyBinary($fileData)) {
            $fileHash = hash('sha256', $fileData);
            $fileSize = strlen($fileData);
            // $path = Storage::putFile('files', $fileData);
            $fileContent = $fileData;
            $path = Storage::put("files/{$fileHash}", $fileContent);
        } else {
            throw new \InvalidArgumentException('Invalid file data provided.');
        }

        $hashKey = hash('sha256', uniqid((string) now(), true));


        $finfo = new \finfo(FILEINFO_MIME_TYPE);
        $mimetype = $finfo->buffer($fileContent);

        // $fileContent = FileContent::create([
        //     'hashkey' => $hashKey,
        //     'filehash' => $fileHash,
        //     'titlename' => $title,
        //     'description' => $description,
        //     'size_in_bytes' => $fileSize,
        //     'content' => $fileContent,
        //     'filelocation' => $filelocation ?? $path,
        //     'created_by' => Auth::id(),
        //     'updated_by' => Auth::id(),
        //     'details' => $details ?? [],
        // ]);

        $fileContentDB = FileContent::where('filehash', $fileHash)->first();

        if ($fileContentDB) {
            return $fileContentDB;
        }





        $fileContentDB = self::insertFileContent([
            'hashkey' => $hashKey,
            'filehash' => $fileHash,
            'titlename' => $title,
            'description' => $description,
            'size_in_bytes' => $fileSize,
            'content' => $fileContent,
            'filelocation' => $filelocation ?? $path,
            'created_by' => Auth::id(),
            'updated_by' => Auth::id(),
            'details' => $details ?? [],
            'mimetype' => $mimetype
        ]);

        return $fileContentDB;
    }


    private static function insertFileList(int $contentuid, string $title, string $filename, string $description, $categories, $details, $tags = [], $hidden = 0, ?string $file_type = null)
    {
        $filecontent_exists = FileContent::where('id', $contentuid)->exists();
        if (!$filecontent_exists) {
            throw new \Exception("File Content Does not Exist", 1);
        }

        $data = [
            'contentuid' => $contentuid,
            'hashkey' => hash('sha256', uniqid((string) now(), true)),
            'title' => $title,
            'filename' => $filename,
            'description' => $description,
            'categories' => $categories,
            'details' => $details,
            'tags' => $tags,
            'hidden' => $hidden,
            'file_type' => $file_type,
            'is_public' => false,
            'created_by' => Auth::id(),
            'updated_by' => Auth::id(),
        ];
        $filelist = FileList::create($data);



        return $filelist;

    }

    public static function uploadFileList(string|UploadedFile $fileData, string $title, string $filename, ?string $description = null, ?array $details = [], $categories = null, $tags = [], $hidden = 0, ?string $file_type = null)
    {
        try {
            $fileContent = self::uploadFileContent($fileData, $title, $description, $details);
            $filecontent_id = $fileContent->id;
            // print_r($fileContent);
            return self::insertFileList($filecontent_id, $title, $filename, $description, $categories, $details, $tags, $hidden, $file_type);
        } catch (\Throwable $th) {
            return Response::json($th->getMessage(), 500);
            // return Response::make('Error uploading file content: ' . $th->getMessage(), 500);
            return false;
        }


    }

    public static function viewFilebyFileListHash(string $filelist_hash)
    {
        $filelist = FileList::where('hashkey', $filelist_hash)->first();

        //    return Response::json($filelist, 500);

        if (!$filelist) {
            abort(404, 'File not found');
        }

        $cdnUrl = trim((string) ($filelist->cdn_url ?? ''));
        if ($cdnUrl !== '') {
            return redirect($cdnUrl);
        }

        $fileContent = $filelist->fileContent;

        if (!$fileContent) {
            abort(404, 'File content not found');
        }

        $content = $fileContent->content;

        $driver = DB::connection()->getDriverName();

        if (is_resource($content) && $driver !== 'mysql') {
            $content = stream_get_contents($content);
        }

        if ($driver === 'mysql') {
            $content = base64_decode($content);
        }


        $finfo = new \finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->buffer($content);

        // Map common MIME types to extensions
        $mimeMap = [
            'image/jpeg' => 'jpg',
            'image/png' => 'png',
            'image/gif' => 'gif',
            'image/webp' => 'webp',
            'application/pdf' => 'pdf',
            'text/plain' => 'txt',
            'text/html' => 'html',
            'application/zip' => 'zip',
            'application/json' => 'json',
            'audio/mpeg' => 'mp3',
            'video/mp4' => 'mp4',
            // add more as needed
        ];

        // Pick extension from map, or fallback
        $extension = $mimeMap[$mimeType] ?? pathinfo($filelist->filename, PATHINFO_EXTENSION);

        // Build safe filename with extension
        $filename = pathinfo($filelist->filename, PATHINFO_FILENAME) . '.' . $extension;

        return Response::make($content, 200, [
            'Content-Type' => $mimeType,
            'Content-Disposition' => 'inline; filename="' . $filename . '"',
            'Content-Length' => strlen($content),
        ]);

        // return Response::make($content, 200, [
        //     'Content-Type' => 'application/octet-stream',
        //     'Content-Disposition' => 'inline; filename="' . $filelist->filename . '"',
        //     'Content-Length' => strlen($content),
        // ]);
    }

    private static function canUploadCategory(string $category): bool
    {
        if (!Auth::check()) {
            return false;
        }

        if (UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::UploadAllFiles)) {
            return true;
        }

        if (strcasecmp($category, 'ProductMarket') === 0) {
            return ProductPermissions::isActionAllowed(UserActions::CreateProductForOwnStore)
                || ProductPermissions::isActionAllowed(UserActions::AddProducttoOwnStore);
        }

        return false;
    }

    public static function UploadFilefromRequest(Request $request, string $category)
    {
        if (!self::canUploadCategory($category)) {
            return response()->json(['error' => 'Unauthorized'], 403);
        }

        if (!$request->hasFile('file')) {
            // $responseData =['success' => false, 'message' => 'No file uploaded'];

            $responseData = false;
            return response()->json(false, 400);
        }


        $file = $request->file('file');
        $filename = $file->getClientFilename();
        $result = self::uploadFileList(
            $file,
            '',
            $filename ?? '',
            '',
            [],
            $category,
            [],
            0,
            null,
        );

        //   return response()->json(['success' => false, 'error' => 'File upload failed'], 500);

        // return $result;
        $file_url = $result->resolvedUrl();

        try {

            if (is_string($result->hashkey) && !empty($result->hashkey)) {
                return response()->json(['success' => true, 'hashkey' => $result->hashkey, 'message' => 'File uploaded successfully', 'url' => $file_url, 'name' => $filename], 200);
            } else {
                throw new \Exception("File upload failed", 1);
            }
        } catch (\Throwable $th) {
            return response()->json(['success' => false, 'error' => 'File upload failed'], 500);
        }

    }



    public static function generateURLforFileListHash(string $filelist_hashkey)
    {
        $cdnUrl = FileList::where('hashkey', $filelist_hashkey)->value('cdn_url');
        if (is_string($cdnUrl) && trim($cdnUrl) !== '') {
            return $cdnUrl;
        }
        return "/RequestData/File/$filelist_hashkey";
        // return route('requestdata.file.view', ['hash' => $filelist_hashkey]);
    }

    public static function bladePreloadFileScript(string|FileList $FileHash): null|string
    {
        try {
            if ($FileHash instanceof FileList) {

                $filecontent = $FileHash->fileContent->content;

                $mimeType = $FileHash->fileContent->mimetype ?? 'application/octet-stream';
                $FileHash = $FileHash->hashkey;
            } else {
                $FileHash = FileList::where('hashkey', $FileHash)->firstOrFail();
                $filecontent = $FileHash->fileContent->content;
                $mimeType = $FileHash->fileContent->mimetype;
                $FileHash = $FileHash->hashkey;
            }
        } catch (\Throwable $th) {
            return '';
        }


        $base64 = base64_encode($filecontent);

        $htmlString = "reqcacheupdateBase64toFile('$FileHash','$base64','$mimeType')";
        return $htmlString;
    }

}
