import { prisma } from '../../config/database';
import type { FriendshipStatus } from '@prisma/client';
import { notificationService } from './notification.service';
import { achievementsService } from './achievements.service';
import { sseService } from './sse.service';

const FRIEND_SELECT = {
  id: true,
  email: true,
  name: true,
  role: true,
  createdAt: true,
} as const;

export const friendshipService = {
  /** Send a friend request (creates pending Friendship) */
  async sendRequest(userId: string, friendId: string) {
    // Validation: cannot friend yourself
    if (userId === friendId) {
      throw Object.assign(new Error('Cannot send friend request to yourself'), { statusCode: 400 });
    }

    // Check target user exists
    const target = await prisma.user.findUnique({ where: { id: friendId }, select: { id: true } });
    if (!target) {
      throw Object.assign(new Error('User not found'), { statusCode: 404 });
    }

    // Check block in either direction
    const blocked = await this.isBlocked(userId, friendId);
    if (blocked) {
      throw Object.assign(new Error('Cannot send friend request'), { statusCode: 403 });
    }

    // Check target privacy settings
    const privacy = await prisma.privacySettings.findUnique({ where: { userId: friendId } });
    if (privacy?.allowFriendRequests === false) {
      throw Object.assign(new Error('This user is not accepting friend requests'), { statusCode: 403 });
    }

    // Check for existing friendship in either direction
    const existing = await prisma.friendship.findFirst({
      where: {
        OR: [
          { userId, friendId },
          { userId: friendId, friendId: userId },
        ],
      },
    });

    if (existing) {
      if (existing.status === 'accepted') {
        throw Object.assign(new Error('Already friends'), { statusCode: 409 });
      }
      if (existing.status === 'pending') {
        // If they already sent us a request, auto-accept
        if (existing.userId === friendId) {
          return this.acceptRequest(userId, existing.id);
        }
        throw Object.assign(new Error('Friend request already sent'), { statusCode: 409 });
      }
      if (existing.status === 'declined') {
        // Allow re-sending after decline — update existing record
        return prisma.friendship.update({
          where: { id: existing.id },
          data: { userId, friendId, status: 'pending', acceptedAt: null },
          include: { user: { select: FRIEND_SELECT }, friend: { select: FRIEND_SELECT } },
        });
      }
    }

    const friendship = await prisma.friendship.create({
      data: { userId, friendId, status: 'pending' },
      include: { user: { select: FRIEND_SELECT }, friend: { select: FRIEND_SELECT } },
    });

    // Notify the recipient
    const senderName = friendship.user.name || friendship.user.email;
    notificationService.createNotification(
      friendId,
      'friend_request',
      'New Friend Request',
      `${senderName} sent you a friend request`,
      { friendshipId: friendship.id, fromUserId: userId },
    ).catch(() => {}); // fire-and-forget

    // Push real-time SSE event to recipient
    sseService.sendToUser(friendId, 'friend_request', {
      friendshipId: friendship.id,
      from: friendship.user,
    });

    return friendship;
  },

  /** Accept an incoming friend request */
  async acceptRequest(userId: string, friendshipId: number) {
    const friendship = await prisma.friendship.findUnique({
      where: { id: friendshipId },
    });

    if (!friendship) {
      throw Object.assign(new Error('Friend request not found'), { statusCode: 404 });
    }
    if (friendship.friendId !== userId) {
      throw Object.assign(new Error('Not your friend request to accept'), { statusCode: 403 });
    }
    if (friendship.status !== 'pending') {
      throw Object.assign(new Error('Request is not pending'), { statusCode: 400 });
    }

    const updated = await prisma.friendship.update({
      where: { id: friendshipId },
      data: { status: 'accepted', acceptedAt: new Date() },
      include: { user: { select: FRIEND_SELECT }, friend: { select: FRIEND_SELECT } },
    });

    // Notify the original sender that their request was accepted
    const accepterName = updated.friend.name || updated.friend.email;
    notificationService.createNotification(
      updated.userId, // the original sender
      'friend_accepted',
      'Friend Request Accepted',
      `${accepterName} accepted your friend request`,
      { friendshipId: updated.id, fromUserId: userId },
    ).catch(() => {});

    // Push real-time SSE event to original sender
    sseService.sendToUser(updated.userId, 'friend_accepted', {
      friendshipId: updated.id,
      friend: updated.friend,
    });

    // Achievement check for both users (fire-and-forget)
    achievementsService.checkAndUnlock(userId, ['social']).catch(() => {});
    achievementsService.checkAndUnlock(updated.userId, ['social']).catch(() => {});

    return updated;
  },

  /** Decline an incoming friend request */
  async declineRequest(userId: string, friendshipId: number) {
    const friendship = await prisma.friendship.findUnique({
      where: { id: friendshipId },
    });

    if (!friendship) {
      throw Object.assign(new Error('Friend request not found'), { statusCode: 404 });
    }
    if (friendship.friendId !== userId) {
      throw Object.assign(new Error('Not your friend request to decline'), { statusCode: 403 });
    }
    if (friendship.status !== 'pending') {
      throw Object.assign(new Error('Request is not pending'), { statusCode: 400 });
    }

    return prisma.friendship.update({
      where: { id: friendshipId },
      data: { status: 'declined' },
    });
  },

  /** Cancel an outgoing friend request */
  async cancelRequest(userId: string, friendshipId: number) {
    const friendship = await prisma.friendship.findUnique({
      where: { id: friendshipId },
    });

    if (!friendship) {
      throw Object.assign(new Error('Friend request not found'), { statusCode: 404 });
    }
    if (friendship.userId !== userId) {
      throw Object.assign(new Error('Not your friend request to cancel'), { statusCode: 403 });
    }
    if (friendship.status !== 'pending') {
      throw Object.assign(new Error('Request is not pending'), { statusCode: 400 });
    }

    await prisma.friendship.delete({ where: { id: friendshipId } });
    return { success: true };
  },

  /** Remove an accepted friendship */
  async unfriend(userId: string, friendId: string) {
    const friendship = await prisma.friendship.findFirst({
      where: {
        OR: [
          { userId, friendId, status: 'accepted' },
          { userId: friendId, friendId: userId, status: 'accepted' },
        ],
      },
    });

    if (!friendship) {
      throw Object.assign(new Error('Not friends'), { statusCode: 404 });
    }

    // Also remove from close friends
    await prisma.$transaction([
      prisma.friendship.delete({ where: { id: friendship.id } }),
      prisma.closeFriend.deleteMany({
        where: {
          OR: [
            { userId, closeFriendId: friendId },
            { userId: friendId, closeFriendId: userId },
          ],
        },
      }),
    ]);

    return { success: true };
  },

  /** List accepted friends (paginated) */
  async listFriends(userId: string, page: number, limit: number) {
    const skip = (page - 1) * limit;

    const [friendships, total] = await Promise.all([
      prisma.friendship.findMany({
        where: {
          OR: [
            { userId, status: 'accepted' },
            { friendId: userId, status: 'accepted' },
          ],
        },
        include: {
          user: { select: FRIEND_SELECT },
          friend: { select: FRIEND_SELECT },
        },
        orderBy: { acceptedAt: 'desc' },
        skip,
        take: limit,
      }),
      prisma.friendship.count({
        where: {
          OR: [
            { userId, status: 'accepted' },
            { friendId: userId, status: 'accepted' },
          ],
        },
      }),
    ]);

    // Return the "other" user for each friendship
    const friends = friendships.map((f) => ({
      friendshipId: f.id,
      acceptedAt: f.acceptedAt,
      user: f.userId === userId ? f.friend : f.user,
    }));

    return {
      friends,
      pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
    };
  },

  /** List incoming pending requests */
  async listPendingReceived(userId: string) {
    const requests = await prisma.friendship.findMany({
      where: { friendId: userId, status: 'pending' },
      include: { user: { select: FRIEND_SELECT } },
      orderBy: { createdAt: 'desc' },
    });

    return requests.map((r) => ({
      friendshipId: r.id,
      createdAt: r.createdAt,
      from: r.user,
    }));
  },

  /** List outgoing pending requests */
  async listPendingSent(userId: string) {
    const requests = await prisma.friendship.findMany({
      where: { userId, status: 'pending' },
      include: { friend: { select: FRIEND_SELECT } },
      orderBy: { createdAt: 'desc' },
    });

    return requests.map((r) => ({
      friendshipId: r.id,
      createdAt: r.createdAt,
      to: r.friend,
    }));
  },

  /** Get friendship status between two users */
  async getFriendshipStatus(userId: string, otherUserId: string) {
    if (userId === otherUserId) {
      return { status: 'self' as const };
    }

    const blocked = await this.isBlocked(userId, otherUserId);
    if (blocked) {
      return { status: 'blocked' as const };
    }

    const friendship = await prisma.friendship.findFirst({
      where: {
        OR: [
          { userId, friendId: otherUserId },
          { userId: otherUserId, friendId: userId },
        ],
      },
    });

    if (!friendship) {
      return { status: 'none' as const };
    }

    const direction = friendship.userId === userId ? 'sent' : 'received';

    return {
      status: friendship.status as FriendshipStatus,
      friendshipId: friendship.id,
      direction,
    };
  },

  /** Get mutual friends between two users */
  async getMutualFriends(userId: string, otherUserId: string) {
    // Get friend IDs for both users
    const [myFriends, theirFriends] = await Promise.all([
      this.getFriendIds(userId),
      this.getFriendIds(otherUserId),
    ]);

    const mySet = new Set(myFriends);
    const mutualIds = theirFriends.filter((id) => mySet.has(id));

    if (mutualIds.length === 0) {
      return { count: 0, users: [] };
    }

    const users = await prisma.user.findMany({
      where: { id: { in: mutualIds } },
      select: FRIEND_SELECT,
      take: 10,
    });

    return { count: mutualIds.length, users };
  },

  /** Get all accepted friend IDs for a user */
  async getFriendIds(userId: string): Promise<string[]> {
    const friendships = await prisma.friendship.findMany({
      where: {
        OR: [
          { userId, status: 'accepted' },
          { friendId: userId, status: 'accepted' },
        ],
      },
      select: { userId: true, friendId: true },
    });

    return friendships.map((f) => (f.userId === userId ? f.friendId : f.userId));
  },

  /** Check if either user has blocked the other */
  async isBlocked(userId: string, otherUserId: string): Promise<boolean> {
    const block = await prisma.userBlock.findFirst({
      where: {
        OR: [
          { userId, blockedUserId: otherUserId },
          { userId: otherUserId, blockedUserId: userId },
        ],
      },
    });
    return !!block;
  },
};
