<?php

namespace App\Services;

use App\Services\Storage\FileStorageInterface;
use App\Services\Storage\LocalStorageDriver;
use App\Services\Storage\S3StorageDriver;
use App\Services\Storage\StorageLogger;
use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Storage;

class FileStorageService
{
    private FileStorageInterface $driver;

    private Storage $config;

    public function __construct(?Storage $config = null)
    {
        $this->config = $config ?? config('Storage');

        StorageLogger::log('SUCCESS', 'File storage service initializing', $this->baseContext());

        try {
            $this->driver = $this->createDriver($this->config->driver);
            StorageLogger::log('SUCCESS', 'File storage service initialized', $this->baseContext([
                'retain_local' => $this->config->retainLocal,
            ]));
        } catch (\Throwable $e) {
            StorageLogger::log('FAILURE', 'File storage service initialization failed', $this->baseContext([
                'error' => $e->getMessage(),
            ]));
            throw $e;
        }
    }

    /**
     * @param array<string, mixed> $extra
     * @return array<string, mixed>
     */
    private function baseContext(array $extra = []): array
    {
        return array_merge([
            'driver' => $this->config->driver,
            'bucket' => $this->config->s3Bucket !== '' ? $this->config->s3Bucket : null,
        ], $extra);
    }

    public function driver(): FileStorageInterface
    {
        return $this->driver;
    }

    public function isS3(): bool
    {
        return $this->config->driver === 's3';
    }

    public function resolveKey(string $module, string $subFolder, string $fileName): string
    {
        $module    = trim($module, '/');
        $subFolder = trim($subFolder, '/');
        $fileName  = ltrim(str_replace('\\', '/', $fileName), '/');

        if ($subFolder === '') {
            return "uploads/{$module}/{$fileName}";
        }

        return "uploads/{$module}/{$subFolder}/{$fileName}";
    }

    public function upload(UploadedFile|string $source, string $key, array $options = []): string
    {
        StorageLogger::log('SUCCESS', 'Service upload started', $this->baseContext([
            'key'    => $key,
            'source' => $source instanceof UploadedFile ? 'uploaded_file' : 'path',
        ]));

        try {
            $storedKey = $this->driver->upload($source, $key, $options);

            if ($this->config->retainLocal && $this->isS3()) {
                StorageLogger::log('SUCCESS', 'Retain local copy started', $this->baseContext([
                    'key' => $key,
                ]));

                try {
                    (new LocalStorageDriver($this->config))->upload($source, $key, $options);
                    StorageLogger::log('SUCCESS', 'Retain local copy complete', $this->baseContext([
                        'key' => $key,
                    ]));
                } catch (\Throwable $e) {
                    StorageLogger::log('WARNING', 'Retain local copy failed after primary upload', $this->baseContext([
                        'key'   => $key,
                        'error' => $e->getMessage(),
                    ]));
                }
            }

            StorageLogger::log('SUCCESS', 'Service upload complete', $this->baseContext([
                'key' => $storedKey,
            ]));

            return $storedKey;
        } catch (\Throwable $e) {
            StorageLogger::log('FAILURE', 'Service upload failed', $this->baseContext([
                'key'   => $key,
                'error' => $e->getMessage(),
            ]));
            throw $e;
        }
    }

    public function uploadModuleFile(
        UploadedFile $file,
        string $module,
        string $subFolder = '',
        ?string $fileName = null
    ): string {
        $fileName = $fileName ?: (time() . '_' . $file->getRandomName());
        $key      = $this->resolveKey($module, $subFolder, $fileName);

        StorageLogger::log('SUCCESS', 'Module upload started', $this->baseContext([
            'module'        => $module,
            'sub_folder'    => $subFolder,
            'file_name'     => $fileName,
            'key'           => $key,
            'original_name' => $file->getClientName(),
            'client_mime'   => $file->getClientMimeType(),
            'size'          => $file->getSize(),
        ]));

        $this->upload($file, $key);

        StorageLogger::log('SUCCESS', 'Module upload complete', $this->baseContext([
            'module'     => $module,
            'sub_folder' => $subFolder,
            'file_name'  => $fileName,
            'key'        => $key,
        ]));

        return $fileName;
    }

    public function put(string $key, string $contents, array $options = []): string
    {
        StorageLogger::log('SUCCESS', 'Service put started', $this->baseContext([
            'key'  => $key,
            'size' => strlen($contents),
        ]));

        try {
            $storedKey = $this->driver->put($key, $contents, $options);

            if ($this->config->retainLocal && $this->isS3()) {
                StorageLogger::log('SUCCESS', 'Retain local put started', $this->baseContext([
                    'key' => $key,
                ]));

                try {
                    (new LocalStorageDriver($this->config))->put($key, $contents, $options);
                    StorageLogger::log('SUCCESS', 'Retain local put complete', $this->baseContext([
                        'key' => $key,
                    ]));
                } catch (\Throwable $e) {
                    StorageLogger::log('WARNING', 'Retain local put failed after primary put', $this->baseContext([
                        'key'   => $key,
                        'error' => $e->getMessage(),
                    ]));
                }
            }

            StorageLogger::log('SUCCESS', 'Service put complete', $this->baseContext([
                'key' => $storedKey,
            ]));

            return $storedKey;
        } catch (\Throwable $e) {
            StorageLogger::log('FAILURE', 'Service put failed', $this->baseContext([
                'key'   => $key,
                'error' => $e->getMessage(),
            ]));
            throw $e;
        }
    }

    public function exists(string $module, string $subFolder, string $fileName): bool
    {
        $key    = $this->resolveKey($module, $subFolder, $fileName);
        $exists = $this->driver->exists($key);

        StorageLogger::log('SUCCESS', 'Service exists check complete', $this->baseContext([
            'module'     => $module,
            'sub_folder' => $subFolder,
            'file_name'  => $fileName,
            'key'        => $key,
            'exists'     => $exists,
        ]));

        return $exists;
    }

    public function existsKey(string $key): bool
    {
        $exists = $this->driver->exists($key);

        StorageLogger::log('SUCCESS', 'Service exists key check complete', $this->baseContext([
            'key'    => $key,
            'exists' => $exists,
        ]));

        return $exists;
    }

    public function delete(string $module, string $subFolder, string $fileName): bool
    {
        $key     = $this->resolveKey($module, $subFolder, $fileName);
        $deleted = $this->driver->delete($key);

        StorageLogger::log($deleted ? 'SUCCESS' : 'WARNING', 'Service delete complete', $this->baseContext([
            'module'     => $module,
            'sub_folder' => $subFolder,
            'file_name'  => $fileName,
            'key'        => $key,
            'deleted'    => $deleted,
        ]));

        return $deleted;
    }

    public function deleteKey(string $key): bool
    {
        $deleted = $this->driver->delete($key);

        StorageLogger::log($deleted ? 'SUCCESS' : 'WARNING', 'Service delete key complete', $this->baseContext([
            'key'     => $key,
            'deleted' => $deleted,
        ]));

        return $deleted;
    }

    public function read(string $module, string $subFolder, string $fileName): string
    {
        $key = $this->resolveKey($module, $subFolder, $fileName);

        StorageLogger::log('SUCCESS', 'Service read started', $this->baseContext([
            'module'     => $module,
            'sub_folder' => $subFolder,
            'file_name'  => $fileName,
            'key'        => $key,
        ]));

        try {
            $contents = $this->driver->read($key);

            StorageLogger::log('SUCCESS', 'Service read complete', $this->baseContext([
                'key'  => $key,
                'size' => strlen($contents),
            ]));

            return $contents;
        } catch (\Throwable $e) {
            StorageLogger::log('FAILURE', 'Service read failed', $this->baseContext([
                'key'   => $key,
                'error' => $e->getMessage(),
            ]));
            throw $e;
        }
    }

    public function readKey(string $key): string
    {
        StorageLogger::log('SUCCESS', 'Service read key started', $this->baseContext([
            'key' => $key,
        ]));

        try {
            $contents = $this->driver->read($key);

            StorageLogger::log('SUCCESS', 'Service read key complete', $this->baseContext([
                'key'  => $key,
                'size' => strlen($contents),
            ]));

            return $contents;
        } catch (\Throwable $e) {
            StorageLogger::log('FAILURE', 'Service read key failed', $this->baseContext([
                'key'   => $key,
                'error' => $e->getMessage(),
            ]));
            throw $e;
        }
    }

    public function download(string $module, string $subFolder, string $fileName): ResponseInterface
    {
        $key = $this->resolveKey($module, $subFolder, $fileName);

        StorageLogger::log('SUCCESS', 'Service download started', $this->baseContext([
            'module'     => $module,
            'sub_folder' => $subFolder,
            'file_name'  => $fileName,
            'key'        => $key,
        ]));

        try {
            $response = $this->driver->streamDownload($key, $fileName);

            StorageLogger::log('SUCCESS', 'Service download complete', $this->baseContext([
                'key'       => $key,
                'file_name' => $fileName,
            ]));

            return $response;
        } catch (\Throwable $e) {
            StorageLogger::log('FAILURE', 'Service download failed', $this->baseContext([
                'key'   => $key,
                'error' => $e->getMessage(),
            ]));
            throw $e;
        }
    }

    public function downloadKey(string $key, ?string $downloadName = null): ResponseInterface
    {
        StorageLogger::log('SUCCESS', 'Service download key started', $this->baseContext([
            'key'           => $key,
            'download_name' => $downloadName ?: basename($key),
        ]));

        try {
            $response = $this->driver->streamDownload($key, $downloadName ?: basename($key));

            StorageLogger::log('SUCCESS', 'Service download key complete', $this->baseContext([
                'key'           => $key,
                'download_name' => $downloadName ?: basename($key),
            ]));

            return $response;
        } catch (\Throwable $e) {
            StorageLogger::log('FAILURE', 'Service download key failed', $this->baseContext([
                'key'   => $key,
                'error' => $e->getMessage(),
            ]));
            throw $e;
        }
    }

    public function getLocalPathForProcessing(string $module, string $subFolder, string $fileName): string
    {
        return $this->getLocalPathForKey($this->resolveKey($module, $subFolder, $fileName));
    }

    public function getLocalPathForKey(string $key): string
    {
        StorageLogger::log('SUCCESS', 'Service local path resolution started', $this->baseContext([
            'key' => $key,
        ]));

        try {
            if ($this->config->driver === 'local') {
                $path = rtrim($this->config->localRoot, '/\\') . DIRECTORY_SEPARATOR . ltrim(str_replace('/', DIRECTORY_SEPARATOR, $key), '/\\');
            } else {
                $path = $this->driver->downloadToTemp($key);
            }

            StorageLogger::log('SUCCESS', 'Service local path resolution complete', $this->baseContext([
                'key'  => $key,
                'path' => $path,
            ]));

            return $path;
        } catch (\Throwable $e) {
            StorageLogger::log('FAILURE', 'Service local path resolution failed', $this->baseContext([
                'key'   => $key,
                'error' => $e->getMessage(),
            ]));
            throw $e;
        }
    }

    /**
     * Delete a path returned by getLocalPathForProcessing / downloadToTemp when it lives
     * under the OS temp directory (S3 working copies). Never deletes local writable/uploads files.
     */
    public function cleanupTempPath(?string $path): bool
    {
        if ($path === null || $path === '') {
            return false;
        }

        if (! is_file($path)) {
            return false;
        }

        if (! $this->isS3TempProcessingPath($path)) {
            return false;
        }

        $deleted = @unlink($path);

        StorageLogger::log($deleted ? 'SUCCESS' : 'WARNING', 'Temp processing path cleanup', $this->baseContext([
            'path'    => $path,
            'deleted' => $deleted,
        ]));

        return $deleted;
    }

    /**
     * True for S3 downloadToTemp paths like /tmp/s3_<uniqid>_<basename>.
     */
    public function isS3TempProcessingPath(string $path): bool
    {
        $tempRoot = rtrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, sys_get_temp_dir()), DIRECTORY_SEPARATOR);
        $normalized = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
        $realTemp = realpath(sys_get_temp_dir());
        if ($realTemp !== false) {
            $tempRoot = rtrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $realTemp), DIRECTORY_SEPARATOR);
        }

        $realPath = realpath($path);
        $checkPath = $realPath !== false
            ? str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $realPath)
            : $normalized;

        if (! str_starts_with($checkPath, $tempRoot . DIRECTORY_SEPARATOR)) {
            return false;
        }

        return str_starts_with(basename($checkPath), 's3_');
    }

    public function getTemporaryUrl(string $module, string $subFolder, string $fileName, ?int $ttlSeconds = null): string
    {
        return $this->getTemporaryUrlForKey(
            $this->resolveKey($module, $subFolder, $fileName),
            $ttlSeconds
        );
    }

    public function getTemporaryUrlForKey(string $key, ?int $ttlSeconds = null): string
    {
        StorageLogger::log('SUCCESS', 'Service temporary URL started', $this->baseContext([
            'key'         => $key,
            'ttl_seconds' => $ttlSeconds ?? $this->config->presignedTtl,
        ]));

        try {
            $url = $this->driver->temporaryUrl($key, $ttlSeconds ?? $this->config->presignedTtl);

            StorageLogger::log('SUCCESS', 'Service temporary URL complete', $this->baseContext([
                'key'         => $key,
                'ttl_seconds' => $ttlSeconds ?? $this->config->presignedTtl,
            ]));

            return $url;
        } catch (\Throwable $e) {
            StorageLogger::log('FAILURE', 'Service temporary URL failed', $this->baseContext([
                'key'   => $key,
                'error' => $e->getMessage(),
            ]));
            throw $e;
        }
    }

    public function getUrl(string $module, string $subFolder, string $fileName): string
    {
        $key = $this->resolveKey($module, $subFolder, $fileName);
        $url = $this->driver->url($key);

        StorageLogger::log('SUCCESS', 'Service URL resolved', $this->baseContext([
            'module'     => $module,
            'sub_folder' => $subFolder,
            'file_name'  => $fileName,
            'key'        => $key,
        ]));

        return $url;
    }

    public function copy(string $fromModule, string $fromSubFolder, string $fromFileName, string $toModule, string $toSubFolder, string $toFileName): bool
    {
        $fromKey = $this->resolveKey($fromModule, $fromSubFolder, $fromFileName);
        $toKey   = $this->resolveKey($toModule, $toSubFolder, $toFileName);

        StorageLogger::log('SUCCESS', 'Service copy started', $this->baseContext([
            'from_key' => $fromKey,
            'to_key'   => $toKey,
        ]));

        try {
            $copied = $this->driver->copy($fromKey, $toKey);

            StorageLogger::log($copied ? 'SUCCESS' : 'FAILURE', 'Service copy complete', $this->baseContext([
                'from_key' => $fromKey,
                'to_key'   => $toKey,
                'copied'   => $copied,
            ]));

            return $copied;
        } catch (\Throwable $e) {
            StorageLogger::log('FAILURE', 'Service copy failed', $this->baseContext([
                'from_key' => $fromKey,
                'to_key'   => $toKey,
                'error'    => $e->getMessage(),
            ]));
            throw $e;
        }
    }

    private function createDriver(string $driver): FileStorageInterface
    {
        return match ($driver) {
            's3'    => new S3StorageDriver($this->config),
            default => new LocalStorageDriver($this->config),
        };
    }
}
