<?php

declare(strict_types=1);

namespace App\Services\Tags;

use App\Models\Tag;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

final class TagDiscoveryService
{
    /**
     * @return array<int, string>
     */
    private function discoverySelectColumns(): array
    {
        return ['tags.id', 'tags.name', 'tags.slug', 'tags.usage_count'];
    }

    public function featuredTags(int $limit = 6, int $windowDays = 14): Collection
    {
        return $this->activeTagsQuery($windowDays)
            ->withCount('artworks')
            ->orderByDesc('recent_clicks')
            ->orderByDesc('usage_count')
            ->orderByDesc('artworks_count')
            ->orderBy('name')
            ->limit($limit)
            ->get();
    }

    public function risingTags(Collection $featuredTags, int $limit = 12, int $windowDays = 14): Collection
    {
        $featuredSlugs = $featuredTags->pluck('slug')->filter()->values();

        $risingTags = $this->activeTagsQuery($windowDays)
            ->withCount('artworks')
            ->when($featuredSlugs->isNotEmpty(), function ($builder) use ($featuredSlugs): void {
                $builder->whereNotIn('tags.slug', $featuredSlugs->all());
            })
            ->when($this->hasDailyMetrics(), function ($builder): void {
                $builder->whereRaw('COALESCE(tag_momentum.recent_clicks, 0) > 0');
            })
            ->orderByDesc('recent_clicks')
            ->orderByDesc('artworks_count')
            ->orderBy('name')
            ->limit($limit)
            ->get();

        if ($risingTags->count() >= $limit) {
            return $risingTags;
        }

        $excludeSlugs = $risingTags
            ->pluck('slug')
            ->merge($featuredSlugs)
            ->filter()
            ->values();

        $fallback = $this->activeTagsQuery($windowDays)
            ->withCount('artworks')
            ->when($excludeSlugs->isNotEmpty(), function ($builder) use ($excludeSlugs): void {
                $builder->whereNotIn('tags.slug', $excludeSlugs->all());
            })
            ->orderByDesc('usage_count')
            ->orderByDesc('artworks_count')
            ->orderBy('name')
            ->limit($limit - $risingTags->count())
            ->get();

        return $risingTags->concat($fallback)->values();
    }

    public function paginatedTags(string $query = '', int $perPage = 48, int $windowDays = 14): LengthAwarePaginator
    {
        $tagsQuery = $this->activeTagsQuery($windowDays)
            ->withCount('artworks');

        if ($query !== '') {
            $tagsQuery->where(function ($builder) use ($query): void {
                $builder
                    ->where('tags.name', 'like', '%' . $query . '%')
                    ->orWhere('tags.slug', 'like', '%' . $query . '%');
            });
        }

        return $tagsQuery
            ->orderByDesc('recent_clicks')
            ->orderByDesc('usage_count')
            ->orderByDesc('artworks_count')
            ->orderBy('name')
            ->paginate($perPage)
            ->withQueryString();
    }

    public function stats(int $matchingTotal, int $windowDays = 14): array
    {
        $activeTags = $this->activeTagsQuery($windowDays);

        return [
            'active' => (clone $activeTags)->count(),
            'usage' => (clone $activeTags)->sum('usage_count'),
            'matching' => $matchingTotal,
            'recent_clicks' => $this->hasDailyMetrics()
                ? (int) DB::table('tag_interaction_daily_metrics')
                    ->whereBetween('metric_date', [$this->windowStartDate($windowDays), now()->toDateString()])
                    ->sum('clicks')
                : 0,
        ];
    }

    public function popularTags(int $limit = 20, int $windowDays = 14): Collection
    {
        return $this->activeTagsQuery($windowDays)
            ->orderByDesc('recent_clicks')
            ->orderByDesc('usage_count')
            ->orderBy('name')
            ->limit($limit)
            ->get();
    }

    public function searchSuggestions(string $query, int $limit = 20, int $windowDays = 14): Collection
    {
        $normalizedQuery = trim($query);

        return $this->activeTagsQuery($windowDays)
            ->when($normalizedQuery !== '', function ($builder) use ($normalizedQuery): void {
                $builder->where(function ($subQuery) use ($normalizedQuery): void {
                    $subQuery->where('tags.name', 'like', $normalizedQuery . '%')
                        ->orWhere('tags.slug', 'like', $normalizedQuery . '%');
                });
            })
            ->orderByDesc('recent_clicks')
            ->orderByDesc('usage_count')
            ->orderBy('name')
            ->limit($limit)
            ->get();
    }

    public function relatedTags(Tag $tag, int $limit = 8, int $windowDays = 14): Collection
    {
        return DB::table('artwork_tag as current_tag')
            ->join('artwork_tag as related_tag', 'related_tag.artwork_id', '=', 'current_tag.artwork_id')
            ->join('tags', 'tags.id', '=', 'related_tag.tag_id')
            ->select([
                'tags.name',
                'tags.slug',
                'tags.usage_count',
            ])
            ->selectRaw('COUNT(DISTINCT current_tag.artwork_id) as shared_artworks_count')
            ->when($this->hasDailyMetrics(), function ($builder) use ($tag, $windowDays): void {
                $transitionMomentum = DB::table('tag_interaction_daily_metrics')
                    ->selectRaw('tag_slug')
                    ->selectRaw('SUM(clicks) AS transition_clicks')
                    ->whereBetween('metric_date', [$this->windowStartDate($windowDays), now()->toDateString()])
                    ->where('surface', '!=', 'recent_search')
                    ->where('source_tag_slug', $tag->slug)
                    ->where('tag_slug', '<>', '')
                    ->groupBy('tag_slug');

                $builder
                    ->leftJoinSub($transitionMomentum, 'tag_transition_momentum', function ($join): void {
                        $join->on('tag_transition_momentum.tag_slug', '=', 'tags.slug');
                    })
                    ->selectRaw('COALESCE(tag_transition_momentum.transition_clicks, 0) as transition_clicks')
                    ->groupBy('tag_transition_momentum.transition_clicks')
                    ->orderByDesc('transition_clicks');
            })
            ->where('current_tag.tag_id', '=', $tag->getKey())
            ->where('related_tag.tag_id', '!=', $tag->getKey())
            ->where('tags.is_active', true)
            ->groupBy('tags.id', 'tags.name', 'tags.slug', 'tags.usage_count')
            ->orderByRaw('COUNT(DISTINCT current_tag.artwork_id) DESC')
            ->orderByDesc('tags.usage_count')
            ->limit($limit)
            ->get();
    }

    private function activeTagsQuery(int $windowDays)
    {
        $query = Tag::query()
            ->select($this->discoverySelectColumns())
            ->where('tags.is_active', true);

        if (! $this->hasDailyMetrics()) {
            return $query->selectRaw('0 AS recent_clicks');
        }

        $tagMomentum = DB::table('tag_interaction_daily_metrics')
            ->selectRaw('tag_slug')
            ->selectRaw('SUM(clicks) AS recent_clicks')
            ->whereBetween('metric_date', [$this->windowStartDate($windowDays), now()->toDateString()])
            ->where('tag_slug', '<>', '')
            ->groupBy('tag_slug');

        return $query
            ->leftJoinSub($tagMomentum, 'tag_momentum', function ($join): void {
                $join->on('tag_momentum.tag_slug', '=', 'tags.slug');
            })
            ->selectRaw('COALESCE(tag_momentum.recent_clicks, 0) AS recent_clicks');
    }

    private function hasDailyMetrics(): bool
    {
        return Schema::hasTable('tag_interaction_daily_metrics');
    }

    private function windowStartDate(int $windowDays): string
    {
        return now()->subDays(max(0, $windowDays - 1))->toDateString();
    }
}
