<?php

declare(strict_types=1);

namespace App\Http\Controllers\Settings;

use App\Http\Controllers\Controller;
use App\Models\Collection;
use App\Models\CollectionSurfaceDefinition;
use App\Models\CollectionSurfacePlacement;
use App\Services\CollectionCampaignService;
use App\Services\CollectionHistoryService;
use App\Services\CollectionService;
use App\Services\CollectionSurfaceService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Inertia\Response;

class CollectionSurfaceController extends Controller
{
    public function __construct(
        private readonly CollectionSurfaceService $surfaces,
        private readonly CollectionService $collections,
        private readonly CollectionHistoryService $history,
        private readonly CollectionCampaignService $campaigns,
    ) {
    }

    public function index(Request $request): Response
    {
        $this->authorizeStaff($request);

        $conflicts = $this->surfaces->placementConflicts();
        $conflictPlacementIds = $conflicts->pluck('placement_ids')->flatten()->unique()->map(fn ($id) => (int) $id)->all();

        $definitions = $this->surfaces->definitions()->map(function ($definition): array {
            return $this->mapDefinition($definition);
        })->values()->all();

        $placements = $this->surfaces->placements()->map(function ($placement) use ($conflictPlacementIds): array {
            return array_merge($this->mapPlacement($placement), [
                'has_conflict' => in_array((int) $placement->id, $conflictPlacementIds, true),
            ]);
        })->values()->all();

        return Inertia::render('Collection/CollectionStaffSurfaces', [
            'definitions' => $definitions,
            'placements' => $placements,
            'conflicts' => $conflicts->all(),
            'surfaceKeyOptions' => collect($definitions)->pluck('surface_key')->values()->all(),
            'collectionOptions' => $this->collections->mapCollectionCardPayloads(
                Collection::query()->public()->orderByDesc('ranking_score')->limit(30)->get(),
                true,
            ),
            'endpoints' => [
                'definitionsStore' => route('settings.collections.surfaces.definitions.store'),
                'definitionsUpdatePattern' => route('settings.collections.surfaces.definitions.update', ['definition' => '__DEFINITION__']),
                'definitionsDeletePattern' => route('settings.collections.surfaces.definitions.destroy', ['definition' => '__DEFINITION__']),
                'placementsStore' => route('settings.collections.surfaces.placements.store'),
                'placementsUpdatePattern' => route('settings.collections.surfaces.placements.update', ['placement' => '__PLACEMENT__']),
                'placementsDeletePattern' => route('settings.collections.surfaces.placements.destroy', ['placement' => '__PLACEMENT__']),
                'previewPattern' => route('settings.collections.surfaces.preview', ['definition' => '__DEFINITION__']),
                'batchEditorial' => route('settings.collections.surfaces.batch-editorial'),
            ],
            'seo' => [
                'title' => 'Collection Surfaces - Skinbase Nova',
                'description' => 'Staff tools for homepage, discovery, and campaign collection surfaces.',
                'canonical' => route('settings.collections.surfaces.index'),
                'robots' => 'noindex,follow',
            ],
        ])->rootView('collections');
    }

    public function storeDefinition(Request $request): JsonResponse
    {
        $this->authorizeStaff($request);

        $definition = $this->surfaces->upsertDefinition($this->validateDefinition($request));

        return response()->json([
            'ok' => true,
            'definition' => $this->mapDefinition($definition->fresh()),
        ]);
    }

    public function updateDefinition(Request $request, CollectionSurfaceDefinition $definition): JsonResponse
    {
        $this->authorizeStaff($request);

        $payload = $this->validateDefinition($request);
        $payload['surface_key'] = $definition->surface_key;

        $updatedDefinition = $this->surfaces->upsertDefinition($payload);

        return response()->json([
            'ok' => true,
            'definition' => $this->mapDefinition($updatedDefinition->fresh()),
        ]);
    }

    public function destroyDefinition(Request $request, CollectionSurfaceDefinition $definition): JsonResponse
    {
        $this->authorizeStaff($request);

        abort_if(
            CollectionSurfacePlacement::query()->where('surface_key', $definition->surface_key)->exists(),
            422,
            'Remove all placements from this surface before deleting the definition.'
        );

        $deletedId = (int) $definition->id;
        $definition->delete();

        return response()->json([
            'ok' => true,
            'deleted_definition_id' => $deletedId,
        ]);
    }

    public function storePlacement(Request $request): JsonResponse
    {
        $this->authorizeStaff($request);

        $payload = $this->validatePlacement($request);
        $payload['created_by_user_id'] = $request->user()?->id;

        $collection = Collection::query()->findOrFail((int) $payload['collection_id']);
        abort_unless($collection->isFeatureablePublicly(), 422, 'Only public, active collections can be placed on public surfaces.');

        $placement = $this->surfaces->upsertPlacement($payload)->loadMissing([
            'collection.user:id,username,name',
            'collection.coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at',
        ]);

        $this->history->record(
            $collection->fresh(),
            $request->user(),
            'placement_assigned',
            'Collection assigned to a staff surface.',
            null,
            $this->placementHistoryPayload($placement)
        );

        return response()->json([
            'ok' => true,
            'placement' => $this->mapPlacement($placement),
            'conflicts' => $this->surfaces->placementConflicts()->all(),
        ]);
    }

    public function updatePlacement(Request $request, CollectionSurfacePlacement $placement): JsonResponse
    {
        $this->authorizeStaff($request);

        $before = $this->placementHistoryPayload($placement);
        $originalCollectionId = (int) $placement->collection_id;
        $payload = $this->validatePlacement($request);
        $payload['id'] = (int) $placement->id;
        $payload['created_by_user_id'] = $placement->created_by_user_id ?: $request->user()?->id;

        $collection = Collection::query()->findOrFail((int) $payload['collection_id']);
        abort_unless($collection->isFeatureablePublicly(), 422, 'Only public, active collections can be placed on public surfaces.');

        $updatedPlacement = $this->surfaces->upsertPlacement($payload)->loadMissing([
            'collection.user:id,username,name',
            'collection.coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at',
        ]);

        if ($originalCollectionId !== (int) $updatedPlacement->collection_id) {
            $originalCollection = Collection::query()->find($originalCollectionId);
            if ($originalCollection) {
                $this->history->record(
                    $originalCollection->fresh(),
                    $request->user(),
                    'placement_removed',
                    'Collection removed from a staff surface assignment.',
                    $before,
                    null
                );
            }

            $this->history->record(
                $collection->fresh(),
                $request->user(),
                'placement_assigned',
                'Collection assigned to a staff surface.',
                null,
                $this->placementHistoryPayload($updatedPlacement)
            );
        } else {
            $this->history->record(
                $collection->fresh(),
                $request->user(),
                'placement_updated',
                'Collection staff surface assignment updated.',
                $before,
                $this->placementHistoryPayload($updatedPlacement)
            );
        }

        return response()->json([
            'ok' => true,
            'placement' => $this->mapPlacement($updatedPlacement),
            'conflicts' => $this->surfaces->placementConflicts()->all(),
        ]);
    }

    public function destroyPlacement(Request $request, CollectionSurfacePlacement $placement): JsonResponse
    {
        $this->authorizeStaff($request);

        $before = $this->placementHistoryPayload($placement);
        $collection = $placement->collection()->first();
        $deletedId = (int) $placement->id;

        $this->surfaces->deletePlacement($placement);

        if ($collection) {
            $this->history->record(
                $collection->fresh(),
                $request->user(),
                'placement_removed',
                'Collection removed from a staff surface assignment.',
                $before,
                null
            );
        }

        return response()->json([
            'ok' => true,
            'deleted_placement_id' => $deletedId,
            'conflicts' => $this->surfaces->placementConflicts()->all(),
        ]);
    }

    public function preview(Request $request, CollectionSurfaceDefinition $definition): JsonResponse
    {
        $this->authorizeStaff($request);

        $items = $this->surfaces->resolveSurfaceItems(
            $definition->surface_key,
            (int) $request->integer('limit', (int) $definition->max_items)
        );

        return response()->json([
            'ok' => true,
            'collections' => $this->collections->mapCollectionCardPayloads($items, true),
        ]);
    }

    public function batchEditorial(Request $request): JsonResponse
    {
        $this->authorizeStaff($request);

        $payload = $this->validateBatchEditorial($request);
        $collectionIds = collect($payload['collection_ids'] ?? [])->map(fn ($id) => (int) $id)->filter()->values()->all();
        abort_if(count($collectionIds) === 0, 422, 'Choose at least one collection for the batch editorial run.');

        if (! ($payload['apply'] ?? false)) {
            return response()->json([
                'ok' => true,
                'mode' => 'preview',
                'plan' => $this->campaigns->batchEditorialPlan($collectionIds, $payload),
            ]);
        }

        $result = DB::transaction(function () use ($collectionIds, $payload, $request): array {
            $actor = $request->user();
            $applied = $this->campaigns->applyBatchEditorialPlan($collectionIds, $payload, $actor);
            $resultsByCollection = collect($applied['results'] ?? [])->keyBy('collection_id');

            foreach ($applied['plan']['items'] as $item) {
                $collectionId = (int) Arr::get($item, 'collection.id');
                $collection = Collection::query()->find($collectionId);
                $itemResult = $resultsByCollection->get($collectionId, []);

                if (! $collection) {
                    continue;
                }

                $summaryParts = [];
                if (count($item['campaign_updates'] ?? []) > 0) {
                    $summaryParts[] = 'campaign metadata refreshed';
                }
                if (is_array($item['placement'] ?? null)) {
                    $summaryParts[] = ($item['placement']['eligible'] ?? false)
                        ? sprintf('placement planned for %s', (string) $item['placement']['surface_key'])
                        : 'placement skipped';
                }

                if (($itemResult['placement']['status'] ?? null) === 'created') {
                    $this->history->record(
                        $collection->fresh(),
                        $actor,
                        'placement_assigned',
                        'Collection assigned to a staff surface via batch editorial tools.',
                        null,
                        $item['placement'] ?? null
                    );
                }

                if (($itemResult['placement']['status'] ?? null) === 'updated') {
                    $this->history->record(
                        $collection->fresh(),
                        $actor,
                        'placement_updated',
                        'Collection staff surface assignment updated via batch editorial tools.',
                        null,
                        $item['placement'] ?? null
                    );
                }

                $this->history->record(
                    $collection->fresh(),
                    $actor,
                    'batch_editorial_updated',
                    'Staff batch editorial tools updated campaign planning.',
                    null,
                    [
                        'summary' => $summaryParts,
                        'campaign_updates' => $item['campaign_updates'] ?? [],
                        'placement' => $item['placement'] ?? null,
                    ]
                );
            }

            return $applied;
        });

        return response()->json([
            'ok' => true,
            'mode' => 'apply',
            'plan' => $result['plan'],
            'results' => $result['results'],
            'placements' => $this->surfaces->placements()->map(fn ($placement): array => $this->mapPlacement($placement))->values()->all(),
            'conflicts' => $this->surfaces->placementConflicts()->all(),
        ]);
    }

    private function authorizeStaff(Request $request): void
    {
        $user = $request->user();

        abort_unless($user && ($user->isAdmin() || $user->isModerator()), 403);
    }

    private function validateDefinition(Request $request): array
    {
        return $request->validate([
            'surface_key' => ['required', 'string', 'max:120'],
            'title' => ['required', 'string', 'max:160'],
            'description' => ['nullable', 'string', 'max:400'],
            'mode' => ['required', 'in:manual,automatic,hybrid'],
            'ranking_mode' => ['required', 'in:ranking_score,recent_activity,quality_score'],
            'max_items' => ['nullable', 'integer', 'min:1', 'max:24'],
            'is_active' => ['nullable', 'boolean'],
            'starts_at' => ['nullable', 'date'],
            'ends_at' => ['nullable', 'date', 'after:starts_at'],
            'fallback_surface_key' => ['nullable', 'string', 'max:120', 'different:surface_key'],
            'rules_json' => ['nullable', 'array'],
        ]);
    }

    private function validatePlacement(Request $request): array
    {
        return $request->validate([
            'id' => ['nullable', 'integer', 'exists:collection_surface_placements,id'],
            'surface_key' => ['required', 'string', 'max:120'],
            'collection_id' => ['required', 'integer', 'exists:collections,id'],
            'placement_type' => ['required', 'in:manual,campaign,scheduled_override'],
            'priority' => ['nullable', 'integer', 'min:-100', 'max:100'],
            'starts_at' => ['nullable', 'date'],
            'ends_at' => ['nullable', 'date', 'after:starts_at'],
            'is_active' => ['nullable', 'boolean'],
            'campaign_key' => ['nullable', 'string', 'max:80'],
            'notes' => ['nullable', 'string', 'max:1000'],
        ]);
    }

    private function validateBatchEditorial(Request $request): array
    {
        return $request->validate([
            'collection_ids' => ['required', 'array', 'min:1', 'max:24'],
            'collection_ids.*' => ['integer', 'distinct', 'exists:collections,id'],
            'campaign_key' => ['nullable', 'string', 'max:80'],
            'campaign_label' => ['nullable', 'string', 'max:120'],
            'event_key' => ['nullable', 'string', 'max:80'],
            'event_label' => ['nullable', 'string', 'max:120'],
            'season_key' => ['nullable', 'string', 'max:80'],
            'banner_text' => ['nullable', 'string', 'max:160'],
            'badge_label' => ['nullable', 'string', 'max:80'],
            'spotlight_style' => ['nullable', 'string', 'max:60'],
            'editorial_notes' => ['nullable', 'string', 'max:4000'],
            'surface_key' => ['nullable', 'string', 'max:120'],
            'placement_type' => ['nullable', 'string', 'in:manual,campaign,scheduled_override'],
            'priority' => ['nullable', 'integer', 'min:-100', 'max:100'],
            'starts_at' => ['nullable', 'date'],
            'ends_at' => ['nullable', 'date', 'after_or_equal:starts_at'],
            'is_active' => ['nullable', 'boolean'],
            'notes' => ['nullable', 'string', 'max:1000'],
            'apply' => ['nullable', 'boolean'],
        ]);
    }

    private function mapDefinition(CollectionSurfaceDefinition $definition): array
    {
        return [
            'id' => (int) $definition->id,
            'surface_key' => $definition->surface_key,
            'title' => $definition->title,
            'description' => $definition->description,
            'mode' => $definition->mode,
            'ranking_mode' => $definition->ranking_mode,
            'max_items' => (int) $definition->max_items,
            'is_active' => (bool) $definition->is_active,
            'starts_at' => $definition->starts_at?->toISOString(),
            'ends_at' => $definition->ends_at?->toISOString(),
            'fallback_surface_key' => $definition->fallback_surface_key,
            'rules_json' => $definition->rules_json,
        ];
    }

    private function mapPlacement(CollectionSurfacePlacement $placement): array
    {
        return [
            'id' => (int) $placement->id,
            'surface_key' => $placement->surface_key,
            'placement_type' => $placement->placement_type,
            'priority' => (int) $placement->priority,
            'starts_at' => $placement->starts_at?->toISOString(),
            'ends_at' => $placement->ends_at?->toISOString(),
            'is_active' => (bool) $placement->is_active,
            'campaign_key' => $placement->campaign_key,
            'notes' => $placement->notes,
            'collection' => $placement->collection
                ? ($this->collections->mapCollectionCardPayloads(collect([$placement->collection]), true)[0] ?? null)
                : null,
        ];
    }

    private function placementHistoryPayload(CollectionSurfacePlacement $placement): array
    {
        return [
            'placement_id' => (int) $placement->id,
            'surface_key' => (string) $placement->surface_key,
            'collection_id' => (int) $placement->collection_id,
            'placement_type' => (string) $placement->placement_type,
            'priority' => (int) $placement->priority,
            'starts_at' => $placement->starts_at?->toISOString(),
            'ends_at' => $placement->ends_at?->toISOString(),
            'is_active' => (bool) $placement->is_active,
            'campaign_key' => $placement->campaign_key,
            'notes' => $placement->notes,
        ];
    }
}
