<?php

declare(strict_types=1);

namespace App\Domain\Media;

use App\Models\Attachment;
use App\Settings\GeneralSettings;
use Illuminate\Http\RedirectResponse;
use Throwable;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use RuntimeException;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;

class AttachmentStorageService
{
    public function uploadLocalFileToDisk(
        string $localPath,
        string $destinationPath,
        ?string $disk = null,
        string $visibility = Attachment::VISIBILITY_PUBLIC,
    ): Attachment {
        if (! is_file($localPath)) {
            throw new RuntimeException("Local file [{$localPath}] does not exist.");
        }

        $disk ??= config('larablog.attachments_disk', 'attachments');
        $normalizedPath = ltrim(str_replace('\\', '/', $destinationPath), '/');
        $mime = mime_content_type($localPath) ?: null;
        $allowed = config('larablog.allowed_attachment_mimes', []);
        if (is_string($mime) && $allowed !== [] && ! in_array($mime, $allowed, true)) {
            throw new RuntimeException("MIME type [{$mime}] is not allowed for attachments.");
        }

        $stream = fopen($localPath, 'r');

        if ($stream === false) {
            throw new RuntimeException("Unable to read local file [{$localPath}].");
        }

        Storage::disk($disk)->put($normalizedPath, $stream, [
            'visibility' => $visibility === Attachment::VISIBILITY_PUBLIC ? 'public' : 'private',
        ]);

        if (is_resource($stream)) {
            fclose($stream);
        }

        return Attachment::query()->create([
            'disk' => $disk,
            'path' => $normalizedPath,
            'filename' => basename($normalizedPath),
            'mime' => $mime,
            'size' => filesize($localPath) ?: 0,
            'checksum' => hash_file('sha256', $localPath) ?: null,
            'visibility' => $visibility,
            'synced_at' => now(),
        ]);
    }

    public function publicUrl(Attachment $attachment): string
    {
        if ($attachment->visibility !== Attachment::VISIBILITY_PUBLIC) {
            return $this->temporaryUrl($attachment);
        }

        $disk = Storage::disk($attachment->disk);

        if (method_exists($disk, 'url')) {
            return $disk->url($attachment->path);
        }

        return $this->temporaryUrl($attachment);
    }

    public function temporaryUrl(Attachment $attachment, int $minutes = 30): string
    {
        return Storage::disk($attachment->disk)->temporaryUrl(
            $attachment->path,
            now()->addMinutes($minutes),
            [
                'ResponseContentDisposition' => 'attachment; filename="'.addslashes($attachment->filename).'"',
            ],
        );
    }

    public function resolveRedirectResponseById(int $id, ?string $ip = null): RedirectResponse|Response
    {
        $attachment = Attachment::query()->find($id);

        if ($attachment === null) {
            abort(SymfonyResponse::HTTP_NOT_FOUND);
        }

        return $this->resolveRedirectResponse($attachment, $ip);
    }

    public function resolveRedirectResponseByLegacyPath(string $legacyPath, ?string $ip = null): RedirectResponse|Response
    {
        $raw = ltrim(str_replace('\\', '/', $legacyPath), '/');
        $normalized = $this->normalizeLegacyPath($legacyPath);
        $prefix = $this->attachmentsUrlPrefix();
        $withPrefix = ($prefix !== '' && ! Str::startsWith($raw, $prefix.'/'))
            ? $prefix.'/'.$raw
            : $raw;

        $candidates = array_values(array_unique(array_filter([$raw, $normalized, $withPrefix])));

        $attachment = Attachment::query()
            ->where(function ($query) use ($candidates) {
                $query->whereIn('legacy_filepath', $candidates)
                    ->orWhereIn('path', $candidates);
            })
            ->first();

        if ($attachment === null) {
            abort(SymfonyResponse::HTTP_NOT_FOUND);
        }

        return $this->resolveRedirectResponse($attachment, $ip);
    }

    public function resolveRedirectResponse(Attachment $attachment, ?string $ip = null): RedirectResponse
    {
        if ($attachment->visibility !== Attachment::VISIBILITY_PUBLIC) {
            abort(SymfonyResponse::HTTP_FORBIDDEN);
        }

        $this->incrementDownloads($attachment, $ip);

        $targetUrl = $attachment->visibility === Attachment::VISIBILITY_PUBLIC
            ? $this->publicUrl($attachment)
            : $this->temporaryUrl($attachment);

        return redirect()->away($targetUrl, SymfonyResponse::HTTP_FOUND);
    }

    public function incrementDownloads(Attachment $attachment, ?string $ip = null): void
    {
        $ip ??= request()->ip() ?? 'unknown';
        $lockKey = "attachment-download:{$attachment->id}:{$ip}";

        $lock = Cache::lock($lockKey, 60);

        if (! $lock->get()) {
            return;
        }

        $attachment->increment('downloads');
    }

    protected function normalizeLegacyPath(string $legacyPath): string
    {
        $path = str_replace('\\', '/', $legacyPath);
        $path = ltrim($path, '/');

        $prefix = $this->attachmentsUrlPrefix();

        if ($prefix !== '' && Str::startsWith($path, $prefix.'/')) {
            $path = Str::after($path, $prefix.'/');
        }

        return $path;
    }

    public function deleteFromDisk(Attachment $attachment): void
    {
        $disk = Storage::disk($attachment->disk);
        if ($attachment->path !== '' && $disk->exists($attachment->path)) {
            $disk->delete($attachment->path);
        }
        if (filled($attachment->thumb_path) && $disk->exists($attachment->thumb_path)) {
            $disk->delete($attachment->thumb_path);
        }
    }

    protected function attachmentsUrlPrefix(): string
    {
        try {
            $fromSettings = app(GeneralSettings::class)->attachments_url_prefix;
            if (is_string($fromSettings) && $fromSettings !== '') {
                return trim($fromSettings, '/');
            }
        } catch (Throwable) {
            // settings unavailable during early boot / migrate
        }

        return trim((string) config('larablog.attachments_url_prefix', 'attachments'), '/');
    }
}
