<?php

namespace Modules\SmartStorage\Entities\Flysystem;

use League\Flysystem\Config;
use League\Flysystem\FileAttributes;
use League\Flysystem\FilesystemAdapter;
use Modules\SmartStorage\Entities\Services\AccessLogger;
use Modules\SmartStorage\Entities\Services\FileEncryptionService;
use Modules\SmartStorage\Entities\SsFile;
use Modules\SmartStorage\Entities\SsSetting;

/**
 * Decorates a raw backend Flysystem adapter (local/s3/dropbox/gdrive) and layers
 * three concerns on top, transparently to all existing upload code:
 *
 *   1. At-rest encryption  — encrypt on write, decrypt on read (per-file IV/tag).
 *   2. File indexing       — record each logical path → backend location + crypto
 *                            metadata in ss_files, so reads can be reversed.
 *   3. Access tracking     — every write/read/delete is written to ss_access_logs.
 *
 * Network/device/OTP *enforcement* deliberately lives in the HTTP serving layer
 * (FileServeController + AccessGuard), not here: server-side reads (PDF logos,
 * etc.) are trusted, while public HTTP requests are gated and can be challenged.
 */
class SmartStorageAdapter implements FilesystemAdapter
{
    public function __construct(
        protected FilesystemAdapter $inner,
        protected SsSetting $settings,
        protected FileEncryptionService $crypto,
        protected AccessLogger $logger
    ) {
    }

    /* ----------------------------------------------------------------------
     | Writes
     * -------------------------------------------------------------------- */

    public function write(string $path, string $contents, Config $config): void
    {
        $this->doWrite($path, $contents, $config);
    }

    public function writeStream(string $path, $contents, Config $config): void
    {
        // When encryption is on we must buffer to transform the bytes. Uploads in
        // this app are images/documents bounded by the core size limit, so this
        // is acceptable. When encryption is off we stream straight through.
        if ($this->encryptionApplies()) {
            $data = stream_get_contents($contents);
            $this->doWrite($path, $data === false ? '' : $data, $config);
            return;
        }

        $this->inner->writeStream($path, $contents, $config);
        $this->indexFile($path, null, false, null, null, null);
        $this->logger->log('write', true, 'stored', $this->findFile($path), $path);
    }

    protected function doWrite(string $path, string $plaintext, Config $config): void
    {
        $size = strlen($plaintext);
        $encrypt = $this->encryptionApplies() && $size <= (int) config('smartstorage.encrypt_max_bytes', 26214400);

        if ($encrypt) {
            $enc = $this->crypto->encrypt($plaintext);
            $this->inner->write($path, $enc['cipher'], $config);
            $this->indexFile($path, $size, true, $enc['iv'], $enc['tag'], $plaintext);
        } else {
            $this->inner->write($path, $plaintext, $config);
            $this->indexFile($path, $size, false, null, null, $plaintext);
        }

        $this->logger->log('write', true, $encrypt ? 'stored_encrypted' : 'stored', $this->findFile($path), $path);
    }

    /* ----------------------------------------------------------------------
     | Reads (trusted; decryption + read logging)
     * -------------------------------------------------------------------- */

    public function read(string $path): string
    {
        $raw = $this->inner->read($path);
        $plain = $this->maybeDecrypt($path, $raw);
        $this->logger->log('read', true, 'read', $this->findFile($path), $path);
        return $plain;
    }

    public function readStream(string $path)
    {
        $file = $this->findFile($path);

        if ($file && $file->is_encrypted) {
            $plain = $this->maybeDecrypt($path, $this->inner->read($path));
            $stream = fopen('php://temp', 'r+');
            fwrite($stream, $plain);
            rewind($stream);
            $this->logger->log('read', true, 'read', $file, $path);
            return $stream;
        }

        $this->logger->log('read', true, 'read', $file, $path);
        return $this->inner->readStream($path);
    }

    protected function maybeDecrypt(string $path, string $raw): string
    {
        $file = $this->findFile($path);
        if ($file && $file->is_encrypted && $file->enc_iv) {
            return $this->crypto->decrypt($raw, $file->enc_iv, (string) $file->enc_tag);
        }
        return $raw;
    }

    /* ----------------------------------------------------------------------
     | Delete / metadata
     * -------------------------------------------------------------------- */

    public function delete(string $path): void
    {
        $file = $this->findFile($path);
        $this->inner->delete($path);
        $this->logger->log('delete', true, 'deleted', $file, $path);
        if ($file) {
            try {
                $file->delete();
            } catch (\Throwable $e) {
            }
        }
    }

    public function fileSize(string $path): FileAttributes
    {
        $file = $this->findFile($path);
        if ($file && $file->size !== null) {
            return new FileAttributes($path, $file->size);
        }
        return $this->inner->fileSize($path);
    }

    public function mimeType(string $path): FileAttributes
    {
        $file = $this->findFile($path);
        if ($file && $file->mime) {
            return new FileAttributes($path, null, null, null, $file->mime);
        }
        return $this->inner->mimeType($path);
    }

    /* ----------------------------------------------------------------------
     | Straight delegation
     * -------------------------------------------------------------------- */

    public function fileExists(string $path): bool
    {
        return $this->inner->fileExists($path);
    }

    public function directoryExists(string $path): bool
    {
        return $this->inner->directoryExists($path);
    }

    public function deleteDirectory(string $path): void
    {
        $this->inner->deleteDirectory($path);
    }

    public function createDirectory(string $path, Config $config): void
    {
        $this->inner->createDirectory($path, $config);
    }

    public function setVisibility(string $path, string $visibility): void
    {
        $this->inner->setVisibility($path, $visibility);
    }

    public function visibility(string $path): FileAttributes
    {
        return $this->inner->visibility($path);
    }

    public function lastModified(string $path): FileAttributes
    {
        return $this->inner->lastModified($path);
    }

    public function listContents(string $path, bool $deep): iterable
    {
        return $this->inner->listContents($path, $deep);
    }

    public function move(string $source, string $destination, Config $config): void
    {
        $this->inner->move($source, $destination, $config);
        $file = $this->findFile($source);
        if ($file) {
            $file->logical_path = $destination;
            $file->backend_path = $destination;
            try {
                $file->save();
            } catch (\Throwable $e) {
            }
        }
    }

    public function copy(string $source, string $destination, Config $config): void
    {
        $this->inner->copy($source, $destination, $config);
        $src = $this->findFile($source);
        if ($src) {
            $clone = $src->replicate();
            $clone->logical_path = $destination;
            $clone->backend_path = $destination;
            try {
                $clone->save();
            } catch (\Throwable $e) {
            }
        }
    }

    /* ----------------------------------------------------------------------
     | Helpers
     * -------------------------------------------------------------------- */

    protected function encryptionApplies(): bool
    {
        return (bool) $this->settings->encryption_enabled;
    }

    protected function findFile(string $path): ?SsFile
    {
        try {
            return SsFile::findByLogicalPath($path, $this->settings->business_id);
        } catch (\Throwable $e) {
            return null;
        }
    }

    protected function indexFile(string $path, ?int $size, bool $encrypted, ?string $iv, ?string $tag, ?string $plaintext): void
    {
        try {
            $mime = null;
            if ($plaintext !== null && function_exists('finfo_open')) {
                $finfo = finfo_open(FILEINFO_MIME_TYPE);
                $mime = finfo_buffer($finfo, $plaintext) ?: null;
                finfo_close($finfo);
            }

            SsFile::updateOrCreate(
                ['logical_path' => $path, 'business_id' => $this->settings->business_id],
                [
                    'backend'      => $this->settings->active_backend,
                    'backend_path' => $path,
                    'is_encrypted' => $encrypted,
                    'enc_algo'     => $encrypted ? $this->crypto->algo() : null,
                    'enc_iv'       => $iv,
                    'enc_tag'      => $tag,
                    'mime'         => $mime,
                    'size'         => $size,
                    'checksum'     => $plaintext !== null ? hash('sha256', $plaintext) : null,
                    'uploaded_by'  => auth()->id(),
                ]
            );
        } catch (\Throwable $e) {
            // indexing failure must not break the upload
        }
    }
}
