<?php

declare(strict_types=1);

namespace App\Http\Controllers\Studio;

use App\Http\Controllers\Controller;
use App\Models\ArtworkVersion;
use App\Services\ArtworkSearchIndexer;
use App\Services\ArtworkVersioningService;
use App\Services\Studio\StudioArtworkQueryService;
use App\Services\Studio\StudioBulkActionService;
use App\Services\Tags\TagDiscoveryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;

/**
 * JSON API endpoints for the Studio artwork manager.
 */
final class StudioArtworksApiController extends Controller
{
    public function __construct(
        private readonly StudioArtworkQueryService $queryService,
        private readonly StudioBulkActionService $bulkService,
        private readonly ArtworkVersioningService $versioningService,
        private readonly ArtworkSearchIndexer $searchIndexer,
        private readonly TagDiscoveryService $tagDiscoveryService,
    ) {}

    /**
     * GET /api/studio/artworks
     * List artworks with search, filter, sort, pagination.
     */
    public function index(Request $request): JsonResponse
    {
        $userId = $request->user()->id;

        $filters = $request->only([
            'q', 'status', 'category', 'tags', 'date_from', 'date_to',
            'performance', 'sort',
        ]);

        $perPage = (int) $request->get('per_page', 24);
        $perPage = min(max($perPage, 12), 100);

        $paginator = $this->queryService->list($userId, $filters, $perPage);

        // Transform the paginator items to a clean DTO
        $items = collect($paginator->items())->map(fn ($artwork) => $this->transformArtwork($artwork));

        return response()->json([
            'data' => $items,
            'meta' => [
                'current_page' => $paginator->currentPage(),
                'last_page'    => $paginator->lastPage(),
                'per_page'     => $paginator->perPage(),
                'total'        => $paginator->total(),
            ],
        ]);
    }

    /**
     * POST /api/studio/artworks/bulk
     * Execute bulk operations.
     */
    public function bulk(Request $request): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'action'      => 'required|string|in:publish,unpublish,archive,unarchive,delete,change_category,add_tags,remove_tags',
            'artwork_ids' => 'required|array|min:1|max:200',
            'artwork_ids.*' => 'integer',
            'params'      => 'sometimes|array',
            'params.category_id' => 'sometimes|integer|exists:categories,id',
            'params.tag_ids'     => 'sometimes|array',
            'params.tag_ids.*'   => 'integer|exists:tags,id',
            'confirm'     => 'required_if:action,delete|string',
        ]);

        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }

        $data = $validator->validated();

        // Require explicit DELETE confirmation
        if ($data['action'] === 'delete' && ($data['confirm'] ?? '') !== 'DELETE') {
            return response()->json([
                'errors' => ['confirm' => ['You must type DELETE to confirm permanent deletion.']],
            ], 422);
        }

        $result = $this->bulkService->execute(
            $request->user()->id,
            $data['action'],
            $data['artwork_ids'],
            $data['params'] ?? [],
        );

        $statusCode = $result['failed'] > 0 && $result['success'] === 0 ? 422 : 200;

        return response()->json($result, $statusCode);
    }

    /**
     * PUT /api/studio/artworks/{id}
     * Update artwork details (title, description, visibility).
     */
    public function update(Request $request, int $id): JsonResponse
    {
        $artwork = $request->user()->artworks()->findOrFail($id);

        $validated = $request->validate([
            'title'       => 'sometimes|string|max:255',
            'description' => 'sometimes|nullable|string|max:5000',
            'is_public'   => 'sometimes|boolean',
            'category_id' => 'sometimes|nullable|integer|exists:categories,id',
            'tags'        => 'sometimes|array|max:15',
            'tags.*'      => 'string|max:64',
        ]);

        if (isset($validated['is_public'])) {
            if ($validated['is_public'] && !$artwork->is_public) {
                $validated['published_at'] = $artwork->published_at ?? now();
            }
        }

        // Extract tags and category before updating core fields
        $tags = $validated['tags'] ?? null;
        $categoryId = $validated['category_id'] ?? null;
        unset($validated['tags'], $validated['category_id']);

        $artwork->update($validated);

        // Sync category
        if ($categoryId !== null) {
            $artwork->categories()->sync([(int) $categoryId]);
        }

        // Sync tags (by slug/name)
        if ($tags !== null) {
            $tagIds = [];
            foreach ($tags as $tagSlug) {
                $tag = \App\Models\Tag::firstOrCreate(
                    ['slug' => \Illuminate\Support\Str::slug($tagSlug)],
                    ['name' => $tagSlug, 'is_active' => true, 'usage_count' => 0]
                );
                $tagIds[$tag->id] = ['source' => 'studio_edit', 'confidence' => 1.0];
            }
            $artwork->tags()->sync($tagIds);
        }

        // Reindex in Meilisearch
        try {
            $artwork->searchable();
        } catch (\Throwable $e) {
            // Meilisearch may be unavailable
        }

        // Reload relationships for response
        $artwork->load(['categories.contentType', 'tags']);
        $primaryCategory = $artwork->categories->first();

        return response()->json([
            'success' => true,
            'artwork' => [
                'id'              => $artwork->id,
                'title'           => $artwork->title,
                'description'     => $artwork->description,
                'is_public'       => (bool) $artwork->is_public,
                'slug'            => $artwork->slug,
                'content_type_id' => $primaryCategory?->contentType?->id,
                'category_id'     => $primaryCategory?->id,
                'tags'            => $artwork->tags->map(fn ($t) => ['id' => $t->id, 'name' => $t->name, 'slug' => $t->slug])->values()->all(),
            ],
        ]);
    }

    /**
     * POST /api/studio/artworks/{id}/toggle
     * Toggle publish/unpublish/archive for a single artwork.
     */
    public function toggle(Request $request, int $id): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'action' => 'required|string|in:publish,unpublish,archive,unarchive',
        ]);

        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }

        $result = $this->bulkService->execute(
            $request->user()->id,
            $validator->validated()['action'],
            [$id],
        );

        if ($result['success'] === 0) {
            return response()->json(['error' => 'Action failed', 'details' => $result['errors']], 404);
        }

        return response()->json(['success' => true]);
    }

    /**
     * GET /api/studio/artworks/{id}/analytics
     * Analytics data for a single artwork.
     */
    public function analytics(Request $request, int $id): JsonResponse
    {
        $artwork = $request->user()->artworks()
            ->with(['stats', 'awardStat'])
            ->findOrFail($id);

        $stats = $artwork->stats;

        return response()->json([
            'artwork' => [
                'id'    => $artwork->id,
                'title' => $artwork->title,
                'slug'  => $artwork->slug,
            ],
            'analytics' => [
                'views'               => (int) ($stats?->views ?? 0),
                'favourites'          => (int) ($stats?->favorites ?? 0),
                'shares'              => (int) ($stats?->shares_count ?? 0),
                'comments'            => (int) ($stats?->comments_count ?? 0),
                'downloads'           => (int) ($stats?->downloads ?? 0),
                'ranking_score'       => (float) ($stats?->ranking_score ?? 0),
                'heat_score'          => (float) ($stats?->heat_score ?? 0),
                'engagement_velocity' => (float) ($stats?->engagement_velocity ?? 0),
            ],
        ]);
    }

    private function transformArtwork($artwork): array
    {
        $stats = $artwork->stats ?? null;

        return [
            'id'              => $artwork->id,
            'title'           => $artwork->title,
            'slug'            => $artwork->slug,
            'thumb_url'       => $artwork->thumbUrl('md') ?? '/images/placeholder.jpg',
            'is_public'       => (bool) $artwork->is_public,
            'is_approved'     => (bool) $artwork->is_approved,
            'published_at'    => $artwork->published_at?->toIso8601String(),
            'created_at'      => $artwork->created_at?->toIso8601String(),
            'deleted_at'      => $artwork->deleted_at?->toIso8601String(),
            'category'        => $artwork->categories->first()?->name,
            'category_slug'   => $artwork->categories->first()?->slug,
            'tags'            => $artwork->tags->pluck('slug')->values()->all(),
            'views'           => (int) ($stats?->views ?? 0),
            'favourites'      => (int) ($stats?->favorites ?? 0),
            'shares'          => (int) ($stats?->shares_count ?? 0),
            'comments'        => (int) ($stats?->comments_count ?? 0),
            'downloads'       => (int) ($stats?->downloads ?? 0),
            'ranking_score'   => (float) ($stats?->ranking_score ?? 0),
            'heat_score'      => (float) ($stats?->heat_score ?? 0),
        ];
    }

    /**
     * GET /api/studio/tags/search?q=...
     * Search active tags for studio pickers, with empty-query fallback to popular tags.
     */
    public function searchTags(Request $request): JsonResponse
    {
        $query = trim((string) $request->input('q'));

        $tags = $this->tagDiscoveryService->searchSuggestions($query, 30);

        return response()->json($tags);
    }

    /**
     * POST /api/studio/artworks/{id}/replace-file
     * Replace the artwork's primary image file — creates a new immutable version.
     *
     * Accepts an optional `change_note` text field alongside the file.
     */
    public function replaceFile(Request $request, int $id): JsonResponse
    {
        $artwork = $request->user()->artworks()->findOrFail($id);

        $request->validate([
            'file'        => 'required|file|mimes:jpeg,jpg,png,webp|max:51200', // 50 MB
            'change_note' => 'sometimes|nullable|string|max:500',
        ]);

        // ── Rate-limit gate (before expensive file processing) ────────────
        try {
            $this->versioningService->rateLimitCheck($request->user()->id, $artwork->id);
        } catch (TooManyRequestsHttpException $e) {
            return response()->json(['success' => false, 'error' => $e->getMessage()], 429);
        }

        $file     = $request->file('file');
        $tempPath = $file->getRealPath();
        $hash     = hash_file('sha256', $tempPath);

        // Reject identical files early (before any disk writes)
        if ($artwork->hash === $hash) {
            return response()->json([
                'success' => false,
                'error'   => 'The uploaded file is identical to the current version.',
            ], 422);
        }

        try {
            $derivatives  = app(\App\Services\Uploads\UploadDerivativesService::class);
            $storage      = app(\App\Services\Uploads\UploadStorageService::class);
            $artworkFiles = app(\App\Repositories\Uploads\ArtworkFileRepository::class);

            // 1. Store original on disk (preserve extension when possible)
            $originalPath     = $derivatives->storeOriginal($tempPath, $hash);
            $origFilename     = basename($originalPath);
            $originalRelative = $storage->sectionRelativePath('original', $hash, $origFilename);
            $origMime         = File::exists($originalPath) ? File::mimeType($originalPath) : 'application/octet-stream';
            $artworkFiles->upsert($artwork->id, 'orig', $originalRelative, $origMime, (int) filesize($originalPath));

            // 2. Generate thumbnails (xs/sm/md/lg/xl)
            $publicAbsolute = $derivatives->generatePublicDerivatives($tempPath, $hash);
            foreach ($publicAbsolute as $variant => $absolutePath) {
                $relativePath = $storage->sectionRelativePath($variant, $hash, $hash . '.webp');
                $artworkFiles->upsert($artwork->id, $variant, $relativePath, 'image/webp', (int) filesize($absolutePath));
            }

            // 3. Get new dimensions
            $dims   = @getimagesize($tempPath);
            $width  = is_array($dims) && isset($dims[0]) ? (int) $dims[0] : $artwork->width;
            $height = is_array($dims) && isset($dims[1]) ? (int) $dims[1] : $artwork->height;
            $size   = (int) filesize($originalPath);

            // 4. Update the artwork's file-serving fields (hash drives thumbnail URLs)
            $origExt = strtolower(pathinfo($originalPath, PATHINFO_EXTENSION) ?: '');
            $displayFileName = $origFilename;

            $clientName = basename(str_replace('\\', '/', (string) $file->getClientOriginalName()));
            $clientName = preg_replace('/[\x00-\x1F\x7F]/', '', (string) $clientName) ?? '';
            $clientName = trim((string) $clientName);

            if ($clientName !== '') {
                $clientExt = strtolower((string) pathinfo($clientName, PATHINFO_EXTENSION));
                if ($clientExt === '' && $origExt !== '') {
                    $clientName .= '.' . $origExt;
                }

                $displayFileName = $clientName;
            }

            $artwork->update([
                'file_name' => $displayFileName,
                'file_path' => '',
                'file_size' => $size,
                'mime_type' => $origMime,
                'hash'      => $hash,
                'file_ext'  => $origExt,
                'thumb_ext' => 'webp',
                'width'     => max(1, $width),
                'height'    => max(1, $height),
            ]);

            // 5. Create version record, apply ranking protection, audit log
            $version = $this->versioningService->createNewVersion(
                $artwork,
                $originalRelative,
                $hash,
                max(1, $width),
                max(1, $height),
                $size,
                $request->user()->id,
                $request->input('change_note'),
            );

            // 6. Reindex in Meilisearch (non-blocking)
            try {
                $this->searchIndexer->update($artwork);
            } catch (\Throwable $e) {
                Log::warning('ArtworkVersioningService: Meilisearch reindex failed', [
                    'artwork_id' => $artwork->id,
                    'error'      => $e->getMessage(),
                ]);
            }

            // 7. CDN cache bust — purge thumbnail paths for the old hash
            $this->purgeCdnCache($artwork, $hash);

            return response()->json([
                'success'        => true,
                'thumb_url'      => $artwork->thumbUrl('md'),
                'thumb_url_lg'   => $artwork->thumbUrl('lg'),
                'width'          => $artwork->width,
                'height'         => $artwork->height,
                'file_size'      => $artwork->file_size,
                'version_number' => $version->version_number,
                'requires_reapproval' => (bool) $artwork->requires_reapproval,
            ]);
        } catch (\Throwable $e) {
            Log::error('replaceFile: processing error', [
                'artwork_id' => $artwork->id,
                'error'      => $e->getMessage(),
            ]);
            return response()->json(['success' => false, 'error' => 'File processing failed: ' . $e->getMessage()], 500);
        }
    }

    /**
     * GET /api/studio/artworks/{id}/versions
     * Return version history for an artwork (newest first).
     */
    public function versions(Request $request, int $id): JsonResponse
    {
        $artwork  = $request->user()->artworks()->findOrFail($id);
        $versions = $artwork->versions()->reorder()->orderByDesc('version_number')->get();

        return response()->json([
            'artwork' => [
                'id'            => $artwork->id,
                'title'         => $artwork->title,
                'version_count' => (int) ($artwork->version_count ?? 1),
            ],
            'versions' => $versions->map(fn (ArtworkVersion $v) => [
                'id'             => $v->id,
                'version_number' => $v->version_number,
                'file_path'      => $v->file_path,
                'file_hash'      => $v->file_hash,
                'width'          => $v->width,
                'height'         => $v->height,
                'file_size'      => $v->file_size,
                'change_note'    => $v->change_note,
                'is_current'     => $v->is_current,
                'created_at'     => $v->created_at?->toIso8601String(),
            ])->values(),
        ]);
    }

    /**
     * POST /api/studio/artworks/{id}/restore/{version_id}
     * Restore an earlier version (cloned as a new current version).
     */
    public function restoreVersion(Request $request, int $id, int $versionId): JsonResponse
    {
        $artwork = $request->user()->artworks()->findOrFail($id);
        $version = ArtworkVersion::where('artwork_id', $artwork->id)->findOrFail($versionId);

        if ($version->is_current) {
            return response()->json(['success' => false, 'error' => 'This version is already the current version.'], 422);
        }

        try {
            $newVersion = $this->versioningService->restoreVersion($version, $artwork, $request->user()->id);

            // Sync artwork file fields back to restored version dimensions
            $artwork->update([
                'width'     => max(1, (int) $version->width),
                'height'    => max(1, (int) $version->height),
                'file_size' => (int) $version->file_size,
            ]);

            $artwork->refresh();

            // Reindex
            try {
                $this->searchIndexer->update($artwork);
            } catch (\Throwable) {}

            return response()->json([
                'success'        => true,
                'version_number' => $newVersion->version_number,
                'message'        => "Version {$version->version_number} has been restored as version {$newVersion->version_number}.",
            ]);
        } catch (TooManyRequestsHttpException $e) {
            return response()->json(['success' => false, 'error' => $e->getMessage()], 429);
        } catch (\Throwable $e) {
            return response()->json(['success' => false, 'error' => 'Restore failed: ' . $e->getMessage()], 500);
        }
    }

    /**
     * Purge CDN thumbnail cache for the artwork.
     *
     * This is best-effort; failures are logged but never fatal.
     * Configure a CDN purge webhook via ARTWORK_CDN_PURGE_URL if needed.
     */
    private function purgeCdnCache(\App\Models\Artwork $artwork, string $oldHash): void
    {
        try {
            $purgeUrl = config('cdn.purge_url');
            if (empty($purgeUrl)) {
                Log::debug('CDN purge skipped — cdn.purge_url not configured', ['artwork_id' => $artwork->id]);
                return;
            }

            $paths = array_map(
                fn (string $size) => "/thumbs/{$oldHash}/{$size}.webp",
                ['sm', 'md', 'lg', 'xl']
            );

            \Illuminate\Support\Facades\Http::timeout(5)->post($purgeUrl, ['paths' => $paths]);
        } catch (\Throwable $e) {
            Log::warning('CDN cache purge failed', ['artwork_id' => $artwork->id, 'error' => $e->getMessage()]);
        }
    }
}
