<?php

namespace App\Services\Storage;

use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Storage;

class LocalStorageDriver implements FileStorageInterface
{
    public function __construct(private readonly Storage $config)
    {
        StorageLogger::log('SUCCESS', 'Local driver initialized', $this->baseContext());
    }

    /**
     * @param array<string, mixed> $extra
     * @return array<string, mixed>
     */
    private function baseContext(array $extra = []): array
    {
        return array_merge([
            'driver' => 'local',
            'root'   => rtrim($this->config->localRoot, '/\\'),
        ], $extra);
    }

    private function fullPath(string $key): string
    {
        $key = ltrim(str_replace('\\', '/', $key), '/');

        return $this->config->localRoot . str_replace('/', DIRECTORY_SEPARATOR, $key);
    }

    private function ensureDirectory(string $fullPath): void
    {
        $directory = dirname($fullPath);

        if (! is_dir($directory) && ! mkdir($directory, 0777, true) && ! is_dir($directory)) {
            StorageLogger::log('FAILURE', 'Unable to create directory', $this->baseContext([
                'directory' => $directory,
            ]));
            throw new FileStorageException("Unable to create directory: {$directory}");
        }

        StorageLogger::log('SUCCESS', 'Directory ensured', $this->baseContext([
            'directory' => $directory,
        ]));
    }

    public function upload(UploadedFile|string $source, string $key, array $options = []): string
    {
        $fullPath = $this->fullPath($key);

        StorageLogger::log('SUCCESS', 'Upload started', $this->baseContext([
            'key'       => $key,
            'full_path' => $fullPath,
        ]));

        $this->ensureDirectory($fullPath);

        if ($source instanceof UploadedFile) {
            if (! $source->isValid()) {
                StorageLogger::log('FAILURE', 'Upload rejected: invalid uploaded file', $this->baseContext([
                    'key'   => $key,
                    'error' => $source->getErrorString() ?: 'Invalid uploaded file',
                ]));
                throw new FileStorageException($source->getErrorString() ?: 'Invalid uploaded file');
            }

            if ($source->hasMoved()) {
                StorageLogger::log('FAILURE', 'Upload rejected: file already moved', $this->baseContext([
                    'key' => $key,
                ]));
                throw new FileStorageException('Uploaded file has already been moved');
            }

            $source->move(dirname($fullPath), basename($fullPath));

            StorageLogger::log('SUCCESS', 'Upload complete', $this->baseContext([
                'key'           => $key,
                'full_path'     => $fullPath,
                'original_name' => $source->getClientName(),
                'size'          => is_file($fullPath) ? (filesize($fullPath) ?: 0) : 0,
            ]));

            return $key;
        }

        if (! is_file($source)) {
            StorageLogger::log('FAILURE', 'Upload rejected: source file not found', $this->baseContext([
                'key'    => $key,
                'source' => $source,
            ]));
            throw new FileStorageException("Source file not found: {$source}");
        }

        if (! copy($source, $fullPath)) {
            StorageLogger::log('FAILURE', 'Upload failed while copying source file', $this->baseContext([
                'key'    => $key,
                'source' => $source,
            ]));
            throw new FileStorageException("Failed to copy file to {$fullPath}");
        }

        StorageLogger::log('SUCCESS', 'Upload complete', $this->baseContext([
            'key'       => $key,
            'full_path' => $fullPath,
            'source'    => $source,
            'size'      => is_file($fullPath) ? (filesize($fullPath) ?: 0) : 0,
        ]));

        return $key;
    }

    public function put(string $key, string $contents, array $options = []): string
    {
        $fullPath = $this->fullPath($key);

        StorageLogger::log('SUCCESS', 'Put started', $this->baseContext([
            'key'       => $key,
            'full_path' => $fullPath,
            'size'      => strlen($contents),
        ]));

        $this->ensureDirectory($fullPath);

        if (file_put_contents($fullPath, $contents) === false) {
            StorageLogger::log('FAILURE', 'Put failed', $this->baseContext([
                'key'       => $key,
                'full_path' => $fullPath,
            ]));
            throw new FileStorageException("Failed to write file: {$fullPath}");
        }

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

        return $key;
    }

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

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

        return $exists;
    }

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

        StorageLogger::log('SUCCESS', 'Delete started', $this->baseContext([
            'key'       => $key,
            'full_path' => $fullPath,
        ]));

        if (! is_file($fullPath)) {
            StorageLogger::log('WARNING', 'Delete skipped: file not found', $this->baseContext([
                'key'       => $key,
                'full_path' => $fullPath,
            ]));

            return false;
        }

        $deleted = unlink($fullPath);

        if ($deleted) {
            StorageLogger::log('SUCCESS', 'Delete complete', $this->baseContext([
                'key'       => $key,
                'full_path' => $fullPath,
            ]));
        } else {
            StorageLogger::log('FAILURE', 'Delete failed', $this->baseContext([
                'key'       => $key,
                'full_path' => $fullPath,
            ]));
        }

        return $deleted;
    }

    public function read(string $key): string
    {
        $fullPath = $this->fullPath($key);

        StorageLogger::log('SUCCESS', 'Read started', $this->baseContext([
            'key'       => $key,
            'full_path' => $fullPath,
        ]));

        if (! is_file($fullPath)) {
            StorageLogger::log('FAILURE', 'Read failed: file not found', $this->baseContext([
                'key'       => $key,
                'full_path' => $fullPath,
            ]));
            throw new FileStorageException("File not found: {$key}");
        }

        $contents = file_get_contents($fullPath);

        if ($contents === false) {
            StorageLogger::log('FAILURE', 'Read failed: unable to read file', $this->baseContext([
                'key'       => $key,
                'full_path' => $fullPath,
            ]));
            throw new FileStorageException("Unable to read file: {$key}");
        }

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

        return $contents;
    }

    public function downloadToTemp(string $key): string
    {
        $fullPath = $this->fullPath($key);

        StorageLogger::log('SUCCESS', 'Local path resolved for processing', $this->baseContext([
            'key'       => $key,
            'full_path' => $fullPath,
        ]));

        if (! is_file($fullPath)) {
            StorageLogger::log('FAILURE', 'Local path resolution failed: file not found', $this->baseContext([
                'key'       => $key,
                'full_path' => $fullPath,
            ]));
            throw new FileStorageException("File not found: {$key}");
        }

        return $fullPath;
    }

    public function streamDownload(string $key, ?string $downloadName = null): ResponseInterface
    {
        $fullPath = $this->fullPath($key);

        StorageLogger::log('SUCCESS', 'Stream download started', $this->baseContext([
            'key'           => $key,
            'full_path'     => $fullPath,
            'download_name' => $downloadName ?: basename($key),
        ]));

        if (! is_file($fullPath)) {
            StorageLogger::log('FAILURE', 'Stream download failed: file not found', $this->baseContext([
                'key'       => $key,
                'full_path' => $fullPath,
            ]));
            throw new FileStorageException("File not found: {$key}");
        }

        StorageLogger::log('SUCCESS', 'Stream download complete', $this->baseContext([
            'key'           => $key,
            'full_path'     => $fullPath,
            'download_name' => $downloadName ?: basename($key),
            'size'          => filesize($fullPath) ?: 0,
        ]));

        return service('response')->download($fullPath, null)->setFileName($downloadName ?: basename($key));
    }

    public function temporaryUrl(string $key, int $ttlSeconds = 900): string
    {
        StorageLogger::log('SUCCESS', 'Temporary URL generation started', $this->baseContext([
            'key'         => $key,
            'ttl_seconds' => $ttlSeconds,
        ]));

        if (! $this->exists($key)) {
            StorageLogger::log('FAILURE', 'Temporary URL generation failed: file not found', $this->baseContext([
                'key' => $key,
            ]));
            throw new FileStorageException("File not found: {$key}");
        }

        $url = base_url(ltrim(str_replace('\\', '/', $key), '/'));

        StorageLogger::log('SUCCESS', 'Temporary URL generation complete', $this->baseContext([
            'key'         => $key,
            'ttl_seconds' => $ttlSeconds,
        ]));

        return $url;
    }

    public function url(string $key): string
    {
        return $this->temporaryUrl($key);
    }

    public function copy(string $fromKey, string $toKey): bool
    {
        $fromPath = $this->fullPath($fromKey);
        $toPath   = $this->fullPath($toKey);

        StorageLogger::log('SUCCESS', 'Copy started', $this->baseContext([
            'from_key'  => $fromKey,
            'to_key'    => $toKey,
            'from_path' => $fromPath,
            'to_path'   => $toPath,
        ]));

        if (! is_file($fromPath)) {
            StorageLogger::log('FAILURE', 'Copy failed: source file not found', $this->baseContext([
                'from_key'  => $fromKey,
                'from_path' => $fromPath,
            ]));
            throw new FileStorageException("Source file not found: {$fromKey}");
        }

        $this->ensureDirectory($toPath);

        $copied = copy($fromPath, $toPath);

        if ($copied) {
            StorageLogger::log('SUCCESS', 'Copy complete', $this->baseContext([
                'from_key' => $fromKey,
                'to_key'   => $toKey,
            ]));
        } else {
            StorageLogger::log('FAILURE', 'Copy failed', $this->baseContext([
                'from_key' => $fromKey,
                'to_key'   => $toKey,
            ]));
        }

        return $copied;
    }
}
