<?php

declare(strict_types=1);

namespace App\Framework\Filesystem\Traits;

use App\Framework\Filesystem\Exceptions\FileNotFoundException;
use App\Framework\Filesystem\Exceptions\FileReadException;
use App\Framework\Filesystem\Exceptions\FileWriteException;

/**
 * Trait für File-Locking Storage-Operationen
 *
 * Implementiert File-Locking für kritische Operationen
 * und verhindert Data Corruption bei Concurrent Access.
 */
trait LockableStorageTrait
{
    public function withExclusiveLock(string $path, callable $operation): mixed
    {
        $resolvedPath = $this->resolvePath($path);
        $handle = @fopen($resolvedPath, 'c+');
        if ($handle === false) {
            throw new FileWriteException($path);
        }

        if (! @flock($handle, LOCK_EX)) {
            @fclose($handle);

            throw new FileWriteException($path);
        }

        try {
            return $operation($handle);
        } finally {
            @flock($handle, LOCK_UN);
            @fclose($handle);
        }
    }

    public function withSharedLock(string $path, callable $operation): mixed
    {
        if (! $this->exists($path)) {
            throw new FileNotFoundException($path);
        }

        $resolvedPath = $this->resolvePath($path);
        $handle = @fopen($resolvedPath, 'r');
        if ($handle === false) {
            throw new FileReadException($path);
        }

        if (! @flock($handle, LOCK_SH)) {
            @fclose($handle);

            throw new FileReadException($path);
        }

        try {
            return $operation($handle);
        } finally {
            @flock($handle, LOCK_UN);
            @fclose($handle);
        }
    }

    public function isLocked(string $path): bool
    {
        if (! $this->exists($path)) {
            return false;
        }

        $resolvedPath = $this->resolvePath($path);
        $handle = @fopen($resolvedPath, 'r');
        if ($handle === false) {
            return false;
        }

        $isLocked = ! @flock($handle, LOCK_EX | LOCK_NB);

        if (! $isLocked) {
            @flock($handle, LOCK_UN);
        }

        @fclose($handle);

        return $isLocked;
    }

    public function putWithLock(string $path, string $content): void
    {
        $this->withExclusiveLock($path, function ($handle) use ($content) {
            if (@ftruncate($handle, 0) === false || @rewind($handle) === false) {
                throw new FileWriteException('Lock operation failed');
            }

            if (@fwrite($handle, $content) === false) {
                throw new FileWriteException('Lock operation failed');
            }
        });
    }

    public function updateWithLock(string $path, callable $updateCallback): void
    {
        $this->withExclusiveLock($path, function ($handle) use ($updateCallback) {
            $originalContent = '';
            if (@rewind($handle) !== false) {
                $originalContent = @stream_get_contents($handle) ?: '';
            }

            $newContent = $updateCallback($originalContent);

            if (@ftruncate($handle, 0) === false || @rewind($handle) === false) {
                throw new FileWriteException('Lock operation failed');
            }

            if (@fwrite($handle, $newContent) === false) {
                throw new FileWriteException('Lock operation failed');
            }
        });
    }

    public function appendWithLock(string $path, string $content): void
    {
        $resolvedPath = $this->resolvePath($path);
        $dir = dirname($resolvedPath);
        if (! is_dir($dir)) {
            $this->createDirectory($path);
        }

        $handle = @fopen($resolvedPath, 'a');
        if ($handle === false) {
            throw new FileWriteException($path);
        }

        if (! @flock($handle, LOCK_EX)) {
            @fclose($handle);

            throw new FileWriteException($path);
        }

        try {
            if (@fwrite($handle, $content) === false) {
                throw new FileWriteException($path);
            }
        } finally {
            @flock($handle, LOCK_UN);
            @fclose($handle);
        }
    }
}
