<?php

namespace App\Services;

use App\Models\User;
use App\Services\Activity\UserActivityService;
use App\Events\Achievements\AchievementCheckRequested;
use App\Services\FollowAnalyticsService;
use App\Support\AvatarUrl;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

/**
 * FollowService
 *
 * Manages follow / unfollow operations on the user_followers table.
 * Convention:
 *   follower_id  = the user doing the following
 *   user_id      = the user being followed
 *
 * Counters in user_statistics are kept in sync atomically inside a transaction.
 */
final class FollowService
{
    public function __construct(
        private readonly XPService $xp,
        private readonly NotificationService $notifications,
        private readonly FollowAnalyticsService $analytics,
    ) {
    }

    /**
     * Follow $targetId on behalf of $actorId.
     *
     * @return bool  true if a new follow was created, false if already following
     *
     * @throws \InvalidArgumentException if self-follow attempted
     */
    public function follow(int $actorId, int $targetId): bool
    {
        if ($actorId === $targetId) {
            throw new \InvalidArgumentException('Cannot follow yourself.');
        }

        $inserted = false;

        DB::transaction(function () use ($actorId, $targetId, &$inserted) {
            $rows = DB::table('user_followers')->insertOrIgnore([
                'user_id'     => $targetId,
                'follower_id' => $actorId,
                'created_at'  => now(),
            ]);

            if ($rows === 0) {
                // Already following – nothing to do
                return;
            }

            $inserted = true;

            // Increment following_count for actor, followers_count for target
            $this->incrementCounter($actorId, 'following_count');
            $this->incrementCounter($targetId, 'followers_count');
        });

        // Record activity event outside the transaction to avoid deadlocks
        if ($inserted) {
            try {
                \App\Models\ActivityEvent::record(
                    actorId: $actorId,
                    type: \App\Models\ActivityEvent::TYPE_FOLLOW,
                    targetType: \App\Models\ActivityEvent::TARGET_USER,
                    targetId: $targetId,
                );
            } catch (\Throwable) {}

            try {
                app(UserActivityService::class)->logFollow($actorId, $targetId);
            } catch (\Throwable) {}

            $targetUser = User::query()->find($targetId);
            $actorUser = User::query()->find($actorId);
            if ($targetUser && $actorUser) {
                $this->notifications->notifyUserFollowed($targetUser->loadMissing('profile'), $actorUser->loadMissing('profile'));
            }

            $this->analytics->recordFollow($actorId, $targetId);
            $this->xp->awardFollowerReceived($targetId, $actorId);
            event(new AchievementCheckRequested($targetId));
        }

        return $inserted;
    }

    /**
     * Unfollow $targetId on behalf of $actorId.
     *
     * @return bool  true if a follow row was removed, false if wasn't following
     */
    public function unfollow(int $actorId, int $targetId): bool
    {
        if ($actorId === $targetId) {
            return false;
        }

        $deleted = false;

        DB::transaction(function () use ($actorId, $targetId, &$deleted) {
            $rows = DB::table('user_followers')
                ->where('user_id', $targetId)
                ->where('follower_id', $actorId)
                ->delete();

            if ($rows === 0) {
                return;
            }

            $deleted = true;

            $this->decrementCounter($actorId, 'following_count');
            $this->decrementCounter($targetId, 'followers_count');
        });

        if ($deleted) {
            $this->analytics->recordUnfollow($actorId, $targetId);
        }

        return $deleted;
    }

    /**
     * Toggle follow state. Returns the new following state.
     */
    public function toggle(int $actorId, int $targetId): bool
    {
        if ($this->isFollowing($actorId, $targetId)) {
            $this->unfollow($actorId, $targetId);
            return false;
        }

        $this->follow($actorId, $targetId);
        return true;
    }

    public function isFollowing(int $actorId, int $targetId): bool
    {
        return DB::table('user_followers')
            ->where('user_id', $targetId)
            ->where('follower_id', $actorId)
            ->exists();
    }

    /**
     * Current followers_count for a user (from cached column, not live count).
     */
    public function followersCount(int $userId): int
    {
        return (int) DB::table('user_statistics')
            ->where('user_id', $userId)
            ->value('followers_count');
    }

    public function followingCount(int $userId): int
    {
        return (int) DB::table('user_statistics')
            ->where('user_id', $userId)
            ->value('following_count');
    }

    public function getMutualFollowers(int $userA, int $userB, int $limit = 13): array
    {
        $rows = DB::table('user_followers as left_follow')
            ->join('user_followers as right_follow', 'right_follow.follower_id', '=', 'left_follow.follower_id')
            ->join('users as u', 'u.id', '=', 'left_follow.follower_id')
            ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
            ->where('left_follow.user_id', $userA)
            ->where('right_follow.user_id', $userB)
            ->where('left_follow.follower_id', '!=', $userA)
            ->where('left_follow.follower_id', '!=', $userB)
            ->whereNull('u.deleted_at')
            ->where('u.is_active', true)
            ->orderByDesc('left_follow.created_at')
            ->limit(max(1, $limit))
            ->select(['u.id', 'u.username', 'u.name', 'up.avatar_hash'])
            ->get();

        return $this->mapUsers($rows);
    }

    public function relationshipContext(int $viewerId, int $targetId): array
    {
        if ($viewerId === $targetId) {
            return [
                'follower_overlap' => null,
                'shared_following' => null,
                'mutual_followers' => [
                    'count' => 0,
                    'users' => [],
                ],
            ];
        }

        $followerOverlap = $this->buildFollowerOverlapSummary($viewerId, $targetId);
        $sharedFollowing = $this->buildSharedFollowingSummary($viewerId, $targetId);
        $mutualFollowers = $this->getMutualFollowers($viewerId, $targetId, 6);

        return [
            'follower_overlap' => $followerOverlap,
            'shared_following' => $sharedFollowing,
            'mutual_followers' => [
                'count' => count($mutualFollowers),
                'users' => $mutualFollowers,
            ],
        ];
    }

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

    private function incrementCounter(int $userId, string $column): void
    {
        DB::table('user_statistics')->updateOrInsert(
            ['user_id' => $userId],
            [
                $column      => DB::raw("COALESCE({$column}, 0) + 1"),
                'updated_at' => now(),
                'created_at' => now(),          // ignored on update
            ]
        );
    }

    private function decrementCounter(int $userId, string $column): void
    {
        DB::table('user_statistics')
            ->where('user_id', $userId)
            ->where($column, '>', 0)
            ->update([
                $column      => DB::raw("{$column} - 1"),
                'updated_at' => now(),
            ]);
    }

    private function buildFollowerOverlapSummary(int $viewerId, int $targetId): ?array
    {
        $preview = DB::table('user_followers as viewer_following')
            ->join('user_followers as target_followers', 'target_followers.follower_id', '=', 'viewer_following.user_id')
            ->join('users as u', 'u.id', '=', 'viewer_following.user_id')
            ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
            ->where('viewer_following.follower_id', $viewerId)
            ->where('target_followers.user_id', $targetId)
            ->whereNull('u.deleted_at')
            ->where('u.is_active', true)
            ->orderByDesc('target_followers.created_at')
            ->limit(3)
            ->select(['u.id', 'u.username', 'u.name', 'up.avatar_hash'])
            ->get();

        if ($preview->isEmpty()) {
            return null;
        }

        $count = DB::table('user_followers as viewer_following')
            ->join('user_followers as target_followers', 'target_followers.follower_id', '=', 'viewer_following.user_id')
            ->where('viewer_following.follower_id', $viewerId)
            ->where('target_followers.user_id', $targetId)
            ->count();

        $lead = $preview->first();
        $label = $count > 1
            ? sprintf('Followed by %s and %d other%s', $lead->username ?? $lead->name ?? 'someone', $count - 1, $count - 1 === 1 ? '' : 's')
            : sprintf('Followed by %s', $lead->username ?? $lead->name ?? 'someone');

        return [
            'count' => (int) $count,
            'label' => $label,
            'users' => $this->mapUsers($preview),
        ];
    }

    private function buildSharedFollowingSummary(int $viewerId, int $targetId): ?array
    {
        $preview = DB::table('user_followers as viewer_following')
            ->join('user_followers as target_following', 'target_following.user_id', '=', 'viewer_following.user_id')
            ->join('users as u', 'u.id', '=', 'viewer_following.user_id')
            ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
            ->where('viewer_following.follower_id', $viewerId)
            ->where('target_following.follower_id', $targetId)
            ->whereNull('u.deleted_at')
            ->where('u.is_active', true)
            ->orderByDesc('viewer_following.created_at')
            ->limit(3)
            ->select(['u.id', 'u.username', 'u.name', 'up.avatar_hash'])
            ->get();

        if ($preview->isEmpty()) {
            return null;
        }

        $count = DB::table('user_followers as viewer_following')
            ->join('user_followers as target_following', 'target_following.user_id', '=', 'viewer_following.user_id')
            ->where('viewer_following.follower_id', $viewerId)
            ->where('target_following.follower_id', $targetId)
            ->count();

        $lead = $preview->first();
        $label = $count > 1
            ? sprintf('You both follow %s and %d other%s', $lead->username ?? $lead->name ?? 'someone', $count - 1, $count - 1 === 1 ? '' : 's')
            : sprintf('You both follow %s', $lead->username ?? $lead->name ?? 'someone');

        return [
            'count' => (int) $count,
            'label' => $label,
            'users' => $this->mapUsers($preview),
        ];
    }

    private function mapUsers(Collection $rows): array
    {
        return $rows->map(fn ($row) => [
            'id' => (int) $row->id,
            'username' => (string) ($row->username ?? ''),
            'name' => (string) ($row->name ?? $row->username ?? ''),
            'profile_url' => '/@' . strtolower((string) ($row->username ?? $row->id)),
            'avatar_url' => AvatarUrl::forUser((int) $row->id, $row->avatar_hash ?? null, 48),
        ])->values()->all();
    }
}
