<?php

namespace App\Services\Member;

use App\Models\ForumLike;
use App\Models\ForumPost;
use App\Models\ForumReply;
use App\Models\ForumReplyRating;
use App\Models\Hub;
use App\Models\User;
use App\Jobs\NotifyForumPost;
use App\Jobs\NotifyForumReply;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

// ══════════════════════════════════════════════════════════════════
//  InPractice — ForumService
//  Location: app/Services/Member/ForumService.php
//
//  All business logic for Ask 4 Members Opinion:
//    index()          — paginated post feed with filters
//    show()           — full post with replies, ratings, likes
//    createPost()     — new forum post in DB transaction
//    updatePost()     — edit own post only
//    deletePost()     — delete own post only
//    closePost()      — post owner closes thread
//    createReply()    — new reply — increments post reply_count
//    updateReply()    — edit own reply only
//    deleteReply()    — delete own reply — decrements post reply_count
//    likePost()       — toggle like on a post (polymorphic)
//    likeReply()      — toggle like on a reply (polymorphic)
//    rateReply()      — 1–5 rating, upsert — updates cached aggregates
//    markBestAnswer() — post owner marks one reply as best answer
//
//  Performance rules:
//    - Replies + their authors loaded in ONE query per post
//    - Current user likes loaded in ONE query, mapped in PHP
//    - Current user ratings loaded in ONE query, mapped in PHP
//    - avg_rating + ratings_count cached on forum_replies row
//    - reply_count cached on forum_posts row
//    - Hub list cached for 1 hour
//    - No N+1 anywhere
// ══════════════════════════════════════════════════════════════════

class ForumService
{
    // ── Index — paginated post feed ────────────────────────────
    // Returns: posts (paginated), allHubs, activeHubId, filters, totalCount

    public function index(User $user, array $filters): array
    {
        $hubId      = (int) ($filters['hub_id'] ?? 0);
        $status     = $filters['status']   ?? '';    // active | closed | ''
        $sort       = $filters['sort']     ?? 'latest'; // latest | most_replies | top_rated
        $search     = trim($filters['search'] ?? '');

        $allHubs = Cache::remember('hubs.active', 3600, fn () =>
            Hub::where('is_active', true)->get(['id', 'name_en', 'name_ar', 'color', 'icon'])
        );

        // Default to user's primary hub if no hub selected
        if (! $hubId) {
            $primaryHub = $user->primaryHub?->hub_id;
            $hubId = $primaryHub ?? $allHubs->first()?->id ?? 0;
        }

        $query = ForumPost::with(['user:id,nickname,profession,highly_rated_replies_count', 'hub:id,name_en,name_ar,color', 'replies:id,forum_post_id,is_best_answer'])
            ->visible()
            ->where('status', '!=', 'removed');

        if ($hubId) {
            $query->where('hub_id', $hubId);
        }

        if ($status) {
            $query->where('status', $status);
        }

        if ($search) {
            $query->where(function ($q) use ($search) {
                $q->where('title', 'like', "%{$search}%")
                  ->orWhere('body', 'like', "%{$search}%");
            });
        }

        // Pinned posts always float to top
        $query->orderByDesc('is_pinned');

        match ($sort) {
            'most_replies' => $query->orderByDesc('reply_count'),
            'top_rated'    => $query->orderByDesc('like_count'),
            default        => $query->orderByDesc('created_at'),
        };

        $posts = $query->paginate(20)->withQueryString();

        // Format each post for Vue
        $posts->getCollection()->transform(fn ($post) =>
            $this->formatPost($post, $user)
        );

        return [
            'posts'       => $posts,
            'allHubs'     => $allHubs->values()->toArray(),
            'activeHubId' => $hubId,
            'filters'     => [
                'hub_id' => $hubId,
                'status' => $status,
                'sort'   => $sort,
                'search' => $search,
            ],
            'totalCount'  => ForumPost::visible()
                ->where('hub_id', $hubId)
                ->where('status', '!=', 'removed')
                ->count(),
        ];
    }

    // ── Create form data ───────────────────────────────────────
    // Returns hub list from cache — no DB call if cache is warm.

    public function createFormData(): array
    {
        $allHubs = Cache::remember('hubs.active', 3600, fn () =>
            Hub::where('is_active', true)->get(['id', 'name_en', 'name_ar', 'color', 'icon'])
        );

        return [
            'allHubs' => $allHubs->map(fn ($hub) => [
                'id'      => $hub->id,
                'name_en' => $hub->name_en,
                'name_ar' => $hub->name_ar,
                'icon'    => $hub->icon,
                'color'   => $hub->color,
            ])->values()->toArray(),
        ];
    }

    // ── Show — full post with replies ──────────────────────────
    // Returns: post, replies (formatted), isOwner, myLikedPostIds, myLikedReplyIds, myReplyRatings

    public function show(User $user, ForumPost $post): array
    {
        // Abort if post is removed
        abort_if($post->status === 'removed', 404);

        // Abort if hub-only post and user not in that hub
        if ($post->visibility === 'hub') {
            $userHubIds = $user->hubs->pluck('id')->toArray();
            abort_if(! in_array($post->hub_id, $userHubIds), 403);
        }

        // Increment view count
        $post->incrementViews();

        // Load post with author and hub
        $post->load([
            'user:id,nickname,profession,highly_rated_replies_count',
            'hub:id,name_en,name_ar,color',
            'replies:id,forum_post_id,is_best_answer',
         ]);

        // Load all active top-level replies with authors
        // Best answer first, then by top-rated, then by created_at
        $replies = ForumReply::where('forum_post_id', $post->id)
            ->where('status', 'active')
            ->whereNull('parent_reply_id')
            ->with(['user:id,nickname,profession,highly_rated_replies_count'])
            ->orderByDesc('is_best_answer')
            ->orderByDesc('avg_rating')
            ->orderBy('created_at')
            ->get();

        // Load all threaded children for replies that have them
        $replyIds = $replies->pluck('id');

        $children = ForumReply::whereIn('parent_reply_id', $replyIds)
            ->where('status', 'active')
            ->with(['user:id,nickname,profession,highly_rated_replies_count'])
            ->orderBy('created_at')
            ->get()
            ->groupBy('parent_reply_id');

        // One query — all likes the current user has given on this post and its replies
        $allLikeableIds = $replyIds->merge([$post->id]);
        $myLikes = ForumLike::where('user_id', $user->id)
            ->whereIn('likeable_id', $allLikeableIds)
            ->get(['likeable_type', 'likeable_id']);

        $myLikedPostIds  = $myLikes->where('likeable_type', ForumPost::class)->pluck('likeable_id')->flip();
        $myLikedReplyIds = $myLikes->where('likeable_type', ForumReply::class)->pluck('likeable_id')->flip();

        // One query — all ratings the current user has given on this post's replies
        $myRatings = ForumReplyRating::whereIn('forum_reply_id', $replyIds)
            ->where('user_id', $user->id)
            ->pluck('rating', 'forum_reply_id');

        // Format replies
        $formattedReplies = $replies->map(function ($reply) use ($user, $children, $myLikedReplyIds, $myRatings) {
            $formatted = $this->formatReply($reply, $user, $myLikedReplyIds, $myRatings);

            // Attach children (one level of threading)
            $formatted['children'] = ($children[$reply->id] ?? collect())
                ->map(fn ($child) => $this->formatReply($child, $user, $myLikedReplyIds, $myRatings))
                ->values()
                ->toArray();

            return $formatted;
        })->values()->toArray();

        return [
            'post'       => $this->formatPost($post, $user),
            'replies'    => $formattedReplies,
            'is_owner'   => $post->user_id === $user->id,
            'i_liked_post' => isset($myLikedPostIds[$post->id]),
        ];
    }

    // ── Create post ────────────────────────────────────────────

    public function createPost(User $user, array $data): ForumPost
    {
        $post = DB::transaction(function () use ($user, $data) {
            return ForumPost::create([
                'user_id'            => $user->id,
                'hub_id'             => $data['hub_id'],
                'title'              => $data['title'],
                'body'               => $data['body'],
                'language'           => $data['language'],
                'visibility'         => $data['visibility'],
                'posted_as_nickname' => $data['posted_as_nickname'] ?? true,
                'tags'               => $data['tags'] ?? null,
                'status'             => 'active',
            ]);
        });

        // Notify members — dispatched to queue, never synchronous
        NotifyForumPost::dispatch($post)->onQueue('notifications');

        return $post;
    }

    // ── Update post — own posts only ───────────────────────────

    public function updatePost(User $user, ForumPost $post, array $data): ForumPost
    {
        abort_if($post->user_id !== $user->id, 403);
        abort_if($post->status === 'closed', 403);

        $post->update([
            'title'      => $data['title'],
            'body'       => $data['body'],
            'visibility' => $data['visibility'],
            'tags'       => $data['tags'] ?? null,
        ]);

        return $post->fresh();
    }

    // ── Delete post — own posts only ───────────────────────────

    public function deletePost(User $user, ForumPost $post): void
    {
        abort_if($post->user_id !== $user->id, 403);

        // Replies cascade-deleted via FK constraint
        $post->delete();
    }

    // ── Close thread — post owner only ─────────────────────────

    public function closePost(User $user, ForumPost $post): void
    {
        abort_if($post->user_id !== $user->id, 403);

        $post->update(['status' => 'closed']);
    }

    // ── Create reply ───────────────────────────────────────────

    public function createReply(User $user, ForumPost $post, array $data): array
    {
        abort_if($post->status === 'closed', 403, __('forum.thread_closed'));
        abort_if($post->status === 'removed', 404);

        $reply = DB::transaction(function () use ($user, $post, $data) {
            $reply = ForumReply::create([
                'forum_post_id'      => $post->id,
                'user_id'            => $user->id,
                'parent_reply_id'    => $data['parent_reply_id'] ?? null,
                'body'               => $data['body'],
                'posted_as_nickname' => $data['posted_as_nickname'] ?? true,
                'status'             => 'active',
            ]);

            // Keep reply_count in sync on the post (cached column)
            $post->increment('reply_count');

                  return $reply;
        });

        // Notify post author — dispatched to queue, never synchronous
        NotifyForumReply::dispatch($reply, $post)->onQueue('notifications');

        $reply->load('user:id,nickname,profession,highly_rated_replies_count');

        return $this->formatReply($reply, $user, collect(), collect());
    }

    // ── Update reply — own replies only ───────────────────────

    public function updateReply(User $user, ForumReply $reply, string $body): array
    {
        abort_if($reply->user_id !== $user->id, 403);
        abort_if($reply->status === 'removed', 404);

        $reply->update(['body' => $body]);

        $reply->load('user:id,nickname,profession,highly_rated_replies_count');

        return $this->formatReply($reply, $user, collect(), collect());
    }

    // ── Delete reply — own replies only ───────────────────────

   public function deleteReply(User $user, ForumReply $reply): void
    {
        abort_if($reply->user_id !== $user->id, 403);

        // Load the parent post BEFORE the transaction opens
        $post = $reply->post;

        DB::transaction(function () use ($user, $reply, $post) {
            // Recalculate badge counter if this reply was highly rated
            if ($reply->avg_rating >= 4.0 && $reply->ratings_count > 0) {
                $this->recalculateUserBadge($reply->user);
            }

            // Decrement post reply count
            $post->decrement('reply_count');

            // Ratings cascade-deleted via FK
            $reply->delete();
        });
    }

    // ── Like post — toggle ─────────────────────────────────────

    public function likePost(User $user, ForumPost $post): array
    {
        $existing = ForumLike::where('user_id', $user->id)
            ->where('likeable_type', ForumPost::class)
            ->where('likeable_id', $post->id)
            ->first();

        if ($existing) {
            $existing->delete();
            $post->decrement('like_count');
            $liked = false;
        } else {
            ForumLike::create([
                'user_id'       => $user->id,
                'likeable_type' => ForumPost::class,
                'likeable_id'   => $post->id,
            ]);
            $post->increment('like_count');
            $liked = true;
        }

        return ['liked' => $liked, 'like_count' => $post->fresh()->like_count];
    }

    // ── Like reply — toggle ────────────────────────────────────

    public function likeReply(User $user, ForumReply $reply): array
    {
        $existing = ForumLike::where('user_id', $user->id)
            ->where('likeable_type', ForumReply::class)
            ->where('likeable_id', $reply->id)
            ->first();

        if ($existing) {
            $existing->delete();
            $reply->decrement('like_count');
            $liked = false;
        } else {
            ForumLike::create([
                'user_id'       => $user->id,
                'likeable_type' => ForumReply::class,
                'likeable_id'   => $reply->id,
            ]);
            $reply->increment('like_count');
            $liked = true;
        }

        return ['liked' => $liked, 'like_count' => $reply->fresh()->like_count];
    }

    // ── Rate a reply (1–5) ─────────────────────────────────────
    // Upsert — member can change their rating
    // Cannot rate own reply

    public function rateReply(User $rater, ForumReply $reply, int $rating): array
    {
        abort_if($reply->user_id === $rater->id, 403);
        abort_if($reply->status === 'removed', 404);

        DB::transaction(function () use ($rater, $reply, $rating) {
            ForumReplyRating::updateOrCreate(
                ['forum_reply_id' => $reply->id, 'user_id' => $rater->id],
                ['rating' => $rating]
            );

            $this->recalculateReplyAggregates($reply);
            
            $this->recalculateUserBadge($reply->user);
        });

        $reply->refresh();

        return [
            'avg_rating'    => round($reply->avg_rating, 1),
            'ratings_count' => $reply->ratings_count,
            'my_rating'     => $rating,
            'badge'         => $reply->badge,
        ];
    }

    // ── Mark best answer — post owner only ────────────────────
    // Clears any existing best answer first — only one allowed per post

    public function markBestAnswer(User $user, ForumPost $post, ForumReply $reply): void
    {
        abort_if($post->user_id !== $user->id, 403);
        abort_if($reply->forum_post_id !== $post->id, 422);
        abort_if($reply->status === 'removed', 404);

        DB::transaction(function () use ($post, $reply) {
            // Clear existing best answer
            ForumReply::where('forum_post_id', $post->id)
                ->where('is_best_answer', true)
                ->update(['is_best_answer' => false]);

            // Set new best answer
            $reply->update(['is_best_answer' => true]);
        });
    }

    // ── Private helpers ───────────────────────────────────────

    private function formatPost(ForumPost $post, User $user): array
    {
        return [
            'id'                 => $post->id,
            'title'              => $post->title,
            'body'               => $post->body,
            'language'           => $post->language,
            'visibility'         => $post->visibility,
            'status'             => $post->status,
            'is_pinned'          => $post->is_pinned,
            'tags'               => $post->tags ?? [],
            'reply_count'        => $post->reply_count,
            'like_count'         => $post->like_count,
            'view_count'         => $post->view_count,
            'has_best_answer'    => (bool) ($post->replies->where('is_best_answer', true)->count() ?? false),
            'created_at'         => $post->created_at->diffForHumans(),
            'created_at_full'    => $post->created_at->format('d M Y, H:i'),
            'is_mine'            => $post->user_id === $user->id,
            'hub' => [
                'id'       => $post->hub->id,
                'name_en'  => $post->hub->name_en,
                'name_ar'  => $post->hub->name_ar,
                'color'    => $post->hub->color,
            ],
            'author' => [
                'nickname'   => $post->user->nickname,
                'profession' => $post->user->profession,
                'badge'      => ForumReply::resolveBadge($post->user->highly_rated_replies_count ?? 0),
            ],
        ];
    }

    private function formatReply(ForumReply $reply, User $user, $myLikedReplyIds, $myRatings): array
    {
        return [
            'id'                 => $reply->id,
            'body'               => $reply->body,
            'is_best_answer'     => $reply->is_best_answer,
            'like_count'         => $reply->like_count,
            'avg_rating'         => round($reply->avg_rating, 1),
            'ratings_count'      => $reply->ratings_count,
            'status'             => $reply->status,
            'parent_reply_id'    => $reply->parent_reply_id,
            'created_at'         => $reply->created_at->diffForHumans(),
            'created_at_full'    => $reply->created_at->format('d M Y, H:i'),
            'was_edited'         => $reply->updated_at->gt($reply->created_at->addMinute()),
            'is_mine'            => $reply->user_id === $user->id,
            'i_liked'            => isset($myLikedReplyIds[$reply->id]),
            'my_rating'          => $myRatings[$reply->id] ?? null,
            'children'           => [], // populated in show()
            'author' => [
                'nickname'   => $reply->user->nickname,
                'profession' => $reply->user->profession,
                'badge'      => ForumReply::resolveBadge($reply->user->highly_rated_replies_count ?? 0),
            ],
        ];
    }

    private function recalculateReplyAggregates(ForumReply $reply): void
    {
        $result = ForumReplyRating::where('forum_reply_id', $reply->id)
            ->selectRaw('COUNT(*) as cnt, AVG(rating) as avg')
            ->first();

        $reply->update([
            'ratings_count' => $result->cnt ?? 0,
            'avg_rating'    => round($result->avg ?? 0, 2),
        ]);
    }

    private function recalculateUserBadge(User $author): void
    {
        // Count how many of this user's replies have avg_rating >= 4
        // and at least 1 rating — prevents gaming
        $count = ForumReply::where('user_id', $author->id)
            ->where('avg_rating', '>=', 4.0)
            ->where('ratings_count', '>', 0)
            ->count();

        $author->update(['highly_rated_replies_count' => $count]);
    }
}
