<?php

declare(strict_types=1);

namespace App\Uploads\Services;

use App\Models\Upload;
use App\Models\User;
use App\Uploads\Exceptions\UploadNotFoundException;
use App\Uploads\Exceptions\UploadOwnershipException;
use App\Uploads\Exceptions\UploadPublishValidationException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use RuntimeException;

final class PublishService
{
    public function __construct(
        private readonly FileMoveService $fileMoveService,
        private readonly SlugService $slugService,
    )
    {
    }

    public function publish(string $uploadId, User $user): Upload
    {
        $upload = Upload::query()->find($uploadId);
        if (! $upload) {
            throw new UploadNotFoundException('Upload not found.');
        }

        $this->validateBeforePublish($upload, $user);

        $mainFile = DB::table('upload_files')
            ->where('upload_id', $uploadId)
            ->where('type', 'main')
            ->orderBy('id')
            ->first(['path', 'hash']);

        if (! $mainFile || empty($mainFile->hash)) {
            throw new UploadPublishValidationException('Main file hash is missing.');
        }

        $hash = strtolower((string) preg_replace('/[^a-z0-9]/', '', (string) $mainFile->hash));
        if ($hash === '' || strlen($hash) < 4) {
            throw new UploadPublishValidationException('Invalid main file hash.');
        }

        $aa = substr($hash, 0, 2);
        $bb = substr($hash, 2, 2);
        $targetDir = "files/artworks/{$aa}/{$bb}/{$hash}";

        $tempPrefix = 'tmp/drafts/' . $uploadId . '/';
        $promoted = false;

        try {
            DB::beginTransaction();

            $this->fileMoveService->promoteDraft($uploadId, $targetDir);
            $promoted = true;

            $files = DB::table('upload_files')->where('upload_id', $uploadId)->get(['id', 'path']);
            foreach ($files as $file) {
                $oldPath = (string) $file->path;
                if (str_starts_with($oldPath, $tempPrefix)) {
                    $newPath = $targetDir . '/' . ltrim(substr($oldPath, strlen($tempPrefix)), '/');
                    DB::table('upload_files')->where('id', $file->id)->update(['path' => $newPath]);
                }
            }

            $upload->status = 'published';
            $upload->processing_state = 'published';
            if (empty($upload->slug)) {
                $upload->slug = $this->slugService->makeSlug((string) ($upload->title ?? ''));
            }
            $upload->published_at = now();
            $upload->final_path = $targetDir;
            $upload->save();

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();

            if ($promoted) {
                Storage::disk('local')->deleteDirectory($targetDir);
            }

            throw $e;
        }

        Storage::disk('local')->deleteDirectory('tmp/drafts/' . $uploadId);

        return Upload::query()->findOrFail($uploadId);
    }

    private function validateBeforePublish(Upload $upload, User $user): void
    {
        if ((int) $upload->user_id !== (int) $user->id) {
            throw new UploadOwnershipException('You do not own this upload.');
        }

        $role = strtolower((string) ($user->role ?? ''));
        $isAdmin = $role === 'admin';

        if (! $isAdmin && (string) ($upload->moderation_status ?? 'pending') !== 'approved') {
            throw new UploadPublishValidationException('Upload requires moderation approval before publish.');
        }

        if ((string) $upload->status !== 'draft') {
            throw new UploadPublishValidationException('Only draft uploads can be published.');
        }

        if (! (bool) $upload->is_scanned) {
            throw new UploadPublishValidationException('Upload must be scanned before publish.');
        }

        if (empty($upload->preview_path)) {
            throw new UploadPublishValidationException('Preview is required before publish.');
        }

        if (! (bool) $upload->has_tags) {
            throw new UploadPublishValidationException('Tag analysis must complete before publish.');
        }

        if (empty($upload->title) || empty($upload->category_id)) {
            throw new UploadPublishValidationException('Title and category are required before publish.');
        }

        if ((string) $upload->type === 'archive') {
            $hasScreenshot = DB::table('upload_files')
                ->where('upload_id', (string) $upload->id)
                ->where('type', 'screenshot')
                ->exists();

            if (! $hasScreenshot) {
                throw new UploadPublishValidationException('Archive uploads require at least one screenshot.');
            }
        }
    }
}
