<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\Artwork;
use App\Models\ArtworkDownload;
use App\Services\ArtworkOriginalFileLocator;
use App\Services\ArtworkStatsService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

final class ArtworkDownloadController extends Controller
{
    /**
     * Allowed original file extensions for secure server-side download.
     *
     * @var array<int, string>
     */
    private const ALLOWED_EXTENSIONS = [
        'jpg',
        'jpeg',
        'png',
        'gif',
        'webp',
        'bmp',
        'tiff',
        'zip',
        'rar',
        '7z',
        'tar',
        'gz',
    ];

    public function __construct(
        private readonly ArtworkStatsService $stats,
        private readonly ArtworkOriginalFileLocator $originalFiles,
    ) {}

    public function __invoke(Request $request, int $id): BinaryFileResponse|Response
    {
        $artwork = Artwork::query()->find($id);

        if (! $artwork) {
            abort(404);
        }

        $filePath = $this->originalFiles->resolveLocalPath($artwork);
        $ext = strtolower(ltrim((string) pathinfo($filePath, PATHINFO_EXTENSION), '.'));

        if ($filePath === '' || ! in_array($ext, self::ALLOWED_EXTENSIONS, true)) {
            abort(404);
        }

        $this->recordDownload($request, $artwork->id);
        $this->incrementDownloadCountIfAvailable($artwork->id);

        try {
            $this->stats->incrementDownloads((int) $artwork->id, 1, defer: false);
        } catch (\Throwable $exception) {
            Log::warning('Failed to increment artwork_stats download counter.', [
                'artwork_id' => $artwork->id,
                'error' => $exception->getMessage(),
            ]);
        }

        if (! File::isFile($filePath)) {
            Log::warning('Artwork original file missing for download.', [
                'artwork_id' => $artwork->id,
                'ext' => $ext,
                'resolved_path' => $filePath,
            ]);

            abort(404);
        }

        $downloadName = $this->buildDownloadFilename((string) $artwork->file_name, $ext);

        // X-Accel-Redirect is safe only when nginx is explicitly configured to
        // map the internal URI to the originals root. Otherwise fallback to the
        // normal Laravel download response.
        $accelUri = $this->resolveAccelUri($filePath);
        if ($accelUri !== null) {
            return response('', 200, [
                'X-Accel-Redirect' => $accelUri,
                'Content-Type' => 'application/octet-stream',
                'Content-Disposition' => 'attachment; filename="' . addslashes($downloadName) . '"',
                'X-Content-Type-Options' => 'nosniff',
            ]);
        }

        return response()->download($filePath, $downloadName);
    }

    private function resolveAccelUri(string $filePath): ?string
    {
        if (! config('app.download_accel_enabled')) {
            return null;
        }

        $accelBase = rtrim((string) config('app.download_accel_path', ''), '/');
        if ($accelBase === '') {
            return null;
        }

        $root = rtrim((string) config('uploads.local_originals_root'), DIRECTORY_SEPARATOR);
        if ($root === '') {
            return null;
        }

        $normalizedRoot = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $root);
        $normalizedFilePath = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $filePath);
        $rootPrefix = $normalizedRoot . DIRECTORY_SEPARATOR;

        if (! str_starts_with($normalizedFilePath, $rootPrefix)) {
            Log::warning('Artwork download accel path skipped because file is outside originals root.', [
                'resolved_path' => $filePath,
                'originals_root' => $root,
            ]);

            return null;
        }

        $relativePath = substr($normalizedFilePath, strlen($normalizedRoot));
        if ($relativePath === false || $relativePath === '') {
            return null;
        }

        return $accelBase . str_replace(DIRECTORY_SEPARATOR, '/', $relativePath);
    }

    private function recordDownload(Request $request, int $artworkId): void
    {
        try {
            $ipAddress = $request->ip();
            $ipBinary = $ipAddress ? @inet_pton($ipAddress) : false;

            ArtworkDownload::query()->create([
                'artwork_id' => $artworkId,
                'user_id' => $request->user()?->id,
                'ip' => $ipBinary !== false ? $ipBinary : null,
                'ip_address' => $ipAddress,
                'user_agent' => mb_substr((string) $request->userAgent(), 0, 1024),
                'referer' => mb_substr((string) $request->headers->get('referer'), 0, 65535),
            ]);
        } catch (\Throwable $exception) {
            Log::warning('Failed to record artwork download analytics.', [
                'artwork_id' => $artworkId,
                'error' => $exception->getMessage(),
            ]);
        }
    }

    private function incrementDownloadCountIfAvailable(int $artworkId): void
    {
        if (! Schema::hasColumn('artworks', 'download_count')) {
            return;
        }

        Artwork::query()->whereKey($artworkId)->increment('download_count');
    }

    private function buildDownloadFilename(string $fileName, string $ext): string
    {
        $name = trim($fileName);
        $name = str_replace(['/', '\\'], '-', $name);
        $name = preg_replace('/[\x00-\x1F\x7F]/', '', $name) ?? '';
        $name = preg_replace('/\s+/', ' ', $name) ?? '';
        $name = trim((string) $name, ". \t\n\r\0\x0B");

        if ($name === '') {
            $name = 'artwork';
        }

        $baseName = pathinfo($name, PATHINFO_FILENAME);
        $baseName = trim((string) $baseName, ". \t\n\r\0\x0B");
        if ($baseName === '') {
            $baseName = 'artwork';
        }

        $brandSuffix = $this->downloadBrandSuffix();

        if ($brandSuffix !== '' && ! Str::contains(Str::lower($baseName), Str::lower($brandSuffix))) {
            $baseName .= ' (' . $brandSuffix . ')';
        }

        return $baseName . '.' . $ext;
    }

    private function downloadBrandSuffix(): string
    {
        $host = (string) parse_url((string) config('app.url'), PHP_URL_HOST);
        $host = strtolower(trim($host));
        $host = preg_replace('/^www\./', '', $host) ?? '';

        if ($host === '' || in_array($host, ['localhost', '127.0.0.1'], true) || str_ends_with($host, '.test')) {
            return 'skinbase.org';
        }

        return $host;
    }
}
