<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Collection;
use App\Models\CollectionSurfacePlacement;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Str;

class CollectionCampaignService
{
    public function __construct(
        private readonly CollectionService $collections,
        private readonly CollectionDiscoveryService $discovery,
        private readonly CollectionSurfaceService $surfaces,
    ) {
    }

    public function updateCampaign(Collection $collection, array $attributes, ?User $actor = null): Collection
    {
        return $this->collections->updateCollection(
            $collection->loadMissing('user'),
            $this->normalizeAttributes($collection, $attributes),
            $actor,
        );
    }

    public function campaignSummary(Collection $collection): array
    {
        return [
            'campaign_key' => $collection->campaign_key,
            'campaign_label' => $collection->campaign_label,
            'event_key' => $collection->event_key,
            'event_label' => $collection->event_label,
            'season_key' => $collection->season_key,
            'spotlight_style' => $collection->spotlight_style,
            'schedule' => [
                'published_at' => $collection->published_at?->toIso8601String(),
                'unpublished_at' => $collection->unpublished_at?->toIso8601String(),
                'expired_at' => $collection->expired_at?->toIso8601String(),
                'is_scheduled' => $collection->published_at?->isFuture() ?? false,
                'is_expiring_soon' => $collection->unpublished_at?->between(now(), now()->addDays(14)) ?? false,
            ],
            'eligibility' => $this->eligibility($collection),
            'surface_assignments' => $this->surfaceAssignments($collection),
            'recommended_surfaces' => $this->suggestedSurfaceAssignments($collection),
            'editorial_notes' => $collection->editorial_notes,
            'staff_commercial_notes' => $collection->staff_commercial_notes,
        ];
    }

    public function eligibility(Collection $collection): array
    {
        $reasons = [];

        if ($collection->visibility !== Collection::VISIBILITY_PUBLIC) {
            $reasons[] = 'Collection must be public before it can drive public campaign surfaces.';
        }

        if ($collection->moderation_status !== Collection::MODERATION_ACTIVE) {
            $reasons[] = 'Only moderation-approved collections are eligible for campaign promotion.';
        }

        if (in_array($collection->lifecycle_state, [
            Collection::LIFECYCLE_DRAFT,
            Collection::LIFECYCLE_SCHEDULED,
            Collection::LIFECYCLE_EXPIRED,
            Collection::LIFECYCLE_HIDDEN,
            Collection::LIFECYCLE_RESTRICTED,
            Collection::LIFECYCLE_UNDER_REVIEW,
        ], true)) {
            $reasons[] = 'Collection lifecycle must be published, featured, or archived before campaign placement.';
        }

        if ($collection->published_at?->isFuture()) {
            $reasons[] = 'Collection publish window has not opened yet.';
        }

        if ($collection->unpublished_at?->lte(now())) {
            $reasons[] = 'Collection campaign window has already ended.';
        }

        return [
            'is_campaign_ready' => count($reasons) === 0,
            'is_publicly_featureable' => $collection->isFeatureablePublicly(),
            'has_campaign_context' => filled($collection->campaign_key) || filled($collection->event_key) || filled($collection->season_key),
            'reasons' => $reasons,
        ];
    }

    public function surfaceAssignments(Collection $collection): array
    {
        return $collection->placements()
            ->orderBy('surface_key')
            ->orderByDesc('priority')
            ->orderBy('starts_at')
            ->get()
            ->map(function ($placement): array {
                return [
                    'id' => (int) $placement->id,
                    'surface_key' => (string) $placement->surface_key,
                    'placement_type' => (string) $placement->placement_type,
                    'priority' => (int) $placement->priority,
                    'campaign_key' => $placement->campaign_key,
                    'starts_at' => $placement->starts_at?->toIso8601String(),
                    'ends_at' => $placement->ends_at?->toIso8601String(),
                    'is_active' => (bool) $placement->is_active,
                    'notes' => $placement->notes,
                ];
            })
            ->values()
            ->all();
    }

    public function suggestedSurfaceAssignments(Collection $collection): array
    {
        $suggestions = [];

        if (filled($collection->campaign_key) || filled($collection->event_key) || filled($collection->season_key)) {
            $suggestions[] = $this->surfaceSuggestion('homepage.featured_collections', 'campaign', 'Campaign-aware collection suitable for the homepage featured rail.', 90);
            $suggestions[] = $this->surfaceSuggestion('discover.featured_collections', 'campaign', 'Campaign metadata makes this collection a strong discover spotlight candidate.', 85);
        }

        if ($collection->type === Collection::TYPE_EDITORIAL) {
            $suggestions[] = $this->surfaceSuggestion('homepage.editorial_collections', 'editorial', 'Editorial ownership makes this collection a homepage editorial fit.', 88);
        }

        if ($collection->type === Collection::TYPE_COMMUNITY) {
            $suggestions[] = $this->surfaceSuggestion('homepage.community_collections', 'community', 'Community curation makes this collection suitable for the community row.', 82);
        }

        if ((float) ($collection->ranking_score ?? 0) >= 60 || (bool) $collection->is_featured) {
            $suggestions[] = $this->surfaceSuggestion('homepage.trending_collections', 'algorithmic', 'Strong ranking signals make this collection a trending candidate.', 76);
        }

        return collect($suggestions)
            ->unique('surface_key')
            ->values()
            ->all();
    }

    public function expiringCampaignsForOwner(User $user, int $days = 14, int $limit = 6): EloquentCollection
    {
        return Collection::query()
            ->with(['user:id,username,name', 'coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at'])
            ->ownedBy((int) $user->id)
            ->whereNotNull('unpublished_at')
            ->whereBetween('unpublished_at', [now(), now()->addDays(max(1, $days))])
            ->orderBy('unpublished_at')
            ->limit(max(1, $limit))
            ->get();
    }

    public function publicLanding(string $campaignKey, int $limit = 18): array
    {
        $normalizedKey = trim($campaignKey);
        $surfaceItems = $this->surfaces->resolveSurfaceItems(sprintf('campaign.%s.featured_collections', $normalizedKey), $limit);
        $collections = $surfaceItems->isNotEmpty()
            ? $surfaceItems
            : $this->discovery->publicCampaignCollections($normalizedKey, $limit);

        $editorialCollections = $this->discovery->publicCampaignCollectionsByType($normalizedKey, Collection::TYPE_EDITORIAL, 6);
        $communityCollections = $this->discovery->publicCampaignCollectionsByType($normalizedKey, Collection::TYPE_COMMUNITY, 6);
        $trendingCollections = $this->discovery->publicTrendingCampaignCollections($normalizedKey, 6);
        $recentCollections = $this->discovery->publicRecentCampaignCollections($normalizedKey, 6);

        $leadCollection = $collections->first();
        $placementSurfaces = CollectionSurfacePlacement::query()
            ->where('campaign_key', $normalizedKey)
            ->where('is_active', true)
            ->where(function ($query): void {
                $query->whereNull('starts_at')->orWhere('starts_at', '<=', now());
            })
            ->where(function ($query): void {
                $query->whereNull('ends_at')->orWhere('ends_at', '>', now());
            })
            ->orderBy('surface_key')
            ->pluck('surface_key')
            ->unique()
            ->values()
            ->all();

        return [
            'campaign' => [
                'key' => $normalizedKey,
                'label' => $leadCollection?->campaign_label ?: Str::headline(str_replace(['_', '-'], ' ', $normalizedKey)),
                'description' => $leadCollection?->banner_text
                    ?: sprintf('Public collections grouped under the %s campaign, including editorial, community, and discovery-ready showcases.', Str::headline(str_replace(['_', '-'], ' ', $normalizedKey))),
                'badge_label' => $leadCollection?->badge_label,
                'event_key' => $leadCollection?->event_key,
                'event_label' => $leadCollection?->event_label,
                'season_key' => $leadCollection?->season_key,
                'active_surface_keys' => $placementSurfaces,
                'collections_count' => $collections->count(),
            ],
            'collections' => $collections,
            'editorial_collections' => $editorialCollections,
            'community_collections' => $communityCollections,
            'trending_collections' => $trendingCollections,
            'recent_collections' => $recentCollections,
        ];
    }

    public function batchEditorialPlan(array $collectionIds, array $attributes): array
    {
        $collections = Collection::query()
            ->with(['user:id,username,name'])
            ->whereIn('id', collect($collectionIds)->map(fn ($id) => (int) $id)->filter()->values()->all())
            ->orderBy('title')
            ->get();

        $campaignAttributes = $this->batchCampaignAttributes($attributes);
        $placementAttributes = $this->batchPlacementAttributes($attributes);
        $surfaceKey = $placementAttributes['surface_key'] ?? null;

        $items = $collections->map(function (Collection $collection) use ($campaignAttributes, $placementAttributes, $surfaceKey): array {
            $campaignPreview = $this->normalizeAttributes($collection, $campaignAttributes);
            $placementEligible = $surfaceKey ? $collection->isFeatureablePublicly() : null;
            $placementReasons = [];

            if ($surfaceKey && ! $collection->isFeatureablePublicly()) {
                $placementReasons[] = 'Collection is not publicly featureable for staff surface placement.';
            }

            return [
                'collection' => [
                    'id' => (int) $collection->id,
                    'title' => (string) $collection->title,
                    'slug' => (string) $collection->slug,
                    'visibility' => (string) $collection->visibility,
                    'lifecycle_state' => (string) $collection->lifecycle_state,
                    'moderation_status' => (string) $collection->moderation_status,
                    'owner' => $collection->user ? [
                        'id' => (int) $collection->user->id,
                        'username' => $collection->user->username,
                        'name' => $collection->user->name,
                    ] : null,
                ],
                'campaign_updates' => $campaignPreview,
                'placement' => $surfaceKey ? [
                    'surface_key' => $surfaceKey,
                    'placement_type' => $placementAttributes['placement_type'] ?? 'campaign',
                    'priority' => (int) ($placementAttributes['priority'] ?? 0),
                    'starts_at' => $placementAttributes['starts_at'] ?? null,
                    'ends_at' => $placementAttributes['ends_at'] ?? null,
                    'is_active' => array_key_exists('is_active', $placementAttributes) ? (bool) $placementAttributes['is_active'] : true,
                    'campaign_key' => $placementAttributes['campaign_key'] ?? ($campaignPreview['campaign_key'] ?? $collection->campaign_key),
                    'notes' => $placementAttributes['notes'] ?? null,
                    'eligible' => $placementEligible,
                    'reasons' => $placementReasons,
                ] : null,
                'existing_assignments' => $this->surfaceAssignments($collection),
                'eligibility' => $this->eligibility($collection),
            ];
        })->values();

        return [
            'collections_count' => $collections->count(),
            'campaign_updates_count' => $items->filter(fn (array $item): bool => count($item['campaign_updates']) > 0)->count(),
            'placement_candidates_count' => $items->filter(fn (array $item): bool => is_array($item['placement']))->count(),
            'placement_eligible_count' => $items->filter(fn (array $item): bool => ($item['placement']['eligible'] ?? false) === true)->count(),
            'items' => $items->all(),
        ];
    }

    public function applyBatchEditorialPlan(array $collectionIds, array $attributes, ?User $actor = null): array
    {
        $plan = $this->batchEditorialPlan($collectionIds, $attributes);
        $campaignAttributes = $this->batchCampaignAttributes($attributes);
        $placementAttributes = $this->batchPlacementAttributes($attributes);
        $results = [];

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

            if (! $collection) {
                continue;
            }

            $updatedCollection = count($campaignAttributes) > 0
                ? $this->updateCampaign($collection, $campaignAttributes, $actor)
                : $collection->fresh();

            $placementResult = null;

            if (is_array($item['placement'])) {
                if (($item['placement']['eligible'] ?? false) === true) {
                    $existingPlacement = CollectionSurfacePlacement::query()
                        ->where('surface_key', $item['placement']['surface_key'])
                        ->where('collection_id', $updatedCollection->id)
                        ->first();

                    $placementPayload = array_merge($placementAttributes, [
                        'id' => $existingPlacement?->id,
                        'surface_key' => $item['placement']['surface_key'],
                        'collection_id' => $updatedCollection->id,
                        'campaign_key' => $item['placement']['campaign_key'],
                        'created_by_user_id' => $existingPlacement?->created_by_user_id ?: $actor?->id,
                    ]);

                    $placement = $this->surfaces->upsertPlacement($placementPayload);
                    $placementResult = [
                        'status' => $existingPlacement ? 'updated' : 'created',
                        'placement_id' => (int) $placement->id,
                        'surface_key' => (string) $placement->surface_key,
                    ];
                } else {
                    $placementResult = [
                        'status' => 'skipped',
                        'reasons' => $item['placement']['reasons'] ?? [],
                    ];
                }
            }

            $results[] = [
                'collection_id' => (int) $updatedCollection->id,
                'campaign_updated' => count($campaignAttributes) > 0,
                'placement' => $placementResult,
            ];
        }

        return [
            'plan' => $plan,
            'results' => $results,
        ];
    }

    private function normalizeAttributes(Collection $collection, array $attributes): array
    {
        if (array_key_exists('campaign_key', $attributes) && blank($attributes['campaign_key']) && ! array_key_exists('campaign_label', $attributes)) {
            $attributes['campaign_label'] = null;
        }

        if (array_key_exists('event_key', $attributes) && blank($attributes['event_key']) && ! array_key_exists('event_label', $attributes)) {
            $attributes['event_label'] = null;
        }

        if (
            filled($attributes['campaign_key'] ?? $collection->campaign_key)
            && blank($attributes['campaign_label'] ?? $collection->campaign_label)
            && filled($attributes['event_label'] ?? $collection->event_label)
        ) {
            $attributes['campaign_label'] = $attributes['event_label'] ?? $collection->event_label;
        }

        return $attributes;
    }

    private function batchCampaignAttributes(array $attributes): array
    {
        return collect([
            'campaign_key',
            'campaign_label',
            'event_key',
            'event_label',
            'season_key',
            'banner_text',
            'badge_label',
            'spotlight_style',
            'editorial_notes',
        ])->reduce(function (array $carry, string $key) use ($attributes): array {
            if (array_key_exists($key, $attributes)) {
                $carry[$key] = $attributes[$key];
            }

            return $carry;
        }, []);
    }

    private function batchPlacementAttributes(array $attributes): array
    {
        return collect([
            'surface_key',
            'placement_type',
            'priority',
            'starts_at',
            'ends_at',
            'is_active',
            'campaign_key',
            'notes',
        ])->reduce(function (array $carry, string $key) use ($attributes): array {
            if (array_key_exists($key, $attributes)) {
                $carry[$key] = $attributes[$key];
            }

            return $carry;
        }, []);
    }

    private function surfaceSuggestion(string $surfaceKey, string $placementType, string $reason, int $priority): array
    {
        return [
            'surface_key' => $surfaceKey,
            'placement_type' => $placementType,
            'reason' => $reason,
            'priority' => $priority,
        ];
    }
}
