import { prisma } from '../../config/database';
import { logger } from '../../utils/logger';

// ─── Types ───────────────────────────────────────────────────────────────────

interface NetworkStats {
  totalUsers: number;
  connectedUsers: number;
  isolatedUsers: number;
  friendships: { total: number; accepted: number; pending: number; declined: number };
  acceptanceRate: number;
  avgFriendsPerUser: number;
  onlineNow: number;
  totalBlocks: number;
}

interface EngagementStats {
  pokes: { last24h: number; last7d: number; last30d: number };
  recommendations: number;
  digestSubscribers: number;
}

interface AchievementSummary {
  totalUnlocks: number;
  byCategory: Record<string, number>;
  recentUnlocks: Array<{
    userId: string;
    userName: string | null;
    achievementId: string;
    unlockedAt: Date;
  }>;
}

interface GroupSummary {
  total: number;
  byType: Record<string, number>;
  avgMembers: number;
  largest: { id: string; name: string; memberCount: number } | null;
}

interface TopUser {
  rank: number;
  userId: string;
  name: string | null;
  email: string;
  friendCount: number;
}

interface FriendshipTrend {
  date: string;
  count: number;
}

export interface SocialDashboardStats {
  network: NetworkStats;
  engagement: EngagementStats;
  achievements: AchievementSummary;
  groups: GroupSummary;
  topConnected: TopUser[];
  friendshipTrends: FriendshipTrend[];
}

export interface GraphNode {
  id: string;
  name: string | null;
  email: string;
  role: string;
  isOnline: boolean;
  friendCount: number;
  achievementCount: number;
  groups: string[];
}

export interface GraphEdge {
  source: string;
  target: string;
  status: string;
  acceptedAt: Date | null;
}

export interface GraphData {
  nodes: GraphNode[];
  edges: GraphEdge[];
}

interface ModerationBlock {
  id: number;
  userId: string;
  userName: string | null;
  userEmail: string;
  blockedUserId: string;
  blockedUserName: string | null;
  blockedUserEmail: string;
  createdAt: Date;
}

interface AchievementStats {
  achievementId: string;
  totalUnlocks: number;
  unlockRate: number;
}

interface NotificationStats {
  type: string;
  total: number;
  read: number;
  readRate: number;
}

interface PrivacyAdoption {
  setting: string;
  enabled: number;
  disabled: number;
  enabledPercent: number;
}

export interface ModerationData {
  blocks: { items: ModerationBlock[]; total: number };
  achievementStats: AchievementStats[];
  notificationStats: NotificationStats[];
  privacyAdoption: PrivacyAdoption[];
}

// ─── Service ─────────────────────────────────────────────────────────────────

export const socialAdminService = {
  /** Get comprehensive social dashboard stats */
  async getDashboardStats(): Promise<SocialDashboardStats> {
    const [
      totalUsers,
      friendshipCounts,
      onlineNow,
      totalBlocks,
      connectedUserIds,
      pokes24h,
      pokes7d,
      pokes30d,
      recommendations,
      digestSubscribers,
      achievementUnlocks,
      recentUnlocks,
      groupStats,
      topConnected,
      friendshipTrends,
    ] = await Promise.all([
      // Total users (non-TEMP)
      prisma.user.count({ where: { role: { not: 'TEMP' }, status: 'ACTIVE' } }),

      // Friendship counts by status
      prisma.friendship.groupBy({
        by: ['status'],
        _count: { id: true },
      }),

      // Online now
      prisma.userPresence.count({ where: { isOnline: true } }),

      // Total blocks
      prisma.userBlock.count(),

      // Connected users (users with at least 1 accepted friendship)
      prisma.$queryRaw<{ count: bigint }[]>`
        SELECT COUNT(DISTINCT uid) AS count FROM (
          SELECT "user_id" AS uid FROM friendships WHERE status = 'accepted'
          UNION
          SELECT "friend_id" AS uid FROM friendships WHERE status = 'accepted'
        ) connected
      `,

      // Pokes 24h
      prisma.poke.count({ where: { createdAt: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) } } }),
      // Pokes 7d
      prisma.poke.count({ where: { createdAt: { gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) } } }),
      // Pokes 30d
      prisma.poke.count({ where: { createdAt: { gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) } } }),

      // Total recommendations
      prisma.videoRecommendation.count(),

      // Digest subscribers
      prisma.notificationPreferences.count({ where: { digestFrequency: { not: 'none' } } }),

      // Achievement unlocks
      prisma.userAchievement.count(),

      // Recent unlocks (last 10)
      prisma.userAchievement.findMany({
        orderBy: { unlockedAt: 'desc' },
        take: 10,
        include: { user: { select: { name: true } } },
      }),

      // Group stats
      this._getGroupSummary(),

      // Top connected users
      this._getTopConnected(10),

      // Friendship trends (last 30 days)
      this._getFriendshipTrends(30),
    ]);

    // Parse friendship counts
    const friendshipByStatus: Record<string, number> = {};
    let totalFriendships = 0;
    for (const row of friendshipCounts) {
      friendshipByStatus[row.status] = row._count.id;
      totalFriendships += row._count.id;
    }

    const accepted = friendshipByStatus['accepted'] ?? 0;
    const pending = friendshipByStatus['pending'] ?? 0;
    const declined = friendshipByStatus['declined'] ?? 0;

    const connectedCount = Number(connectedUserIds[0]?.count ?? 0);

    // Achievement categories — group by parsing achievementId prefix
    const achievementsByCategory: Record<string, number> = {};
    const allUnlocks = await prisma.userAchievement.groupBy({
      by: ['achievementId'],
      _count: { id: true },
    });
    for (const row of allUnlocks) {
      // Map achievement IDs to categories
      const category = this._achievementCategory(row.achievementId);
      achievementsByCategory[category] = (achievementsByCategory[category] ?? 0) + row._count.id;
    }

    return {
      network: {
        totalUsers,
        connectedUsers: connectedCount,
        isolatedUsers: totalUsers - connectedCount,
        friendships: { total: totalFriendships, accepted, pending, declined },
        acceptanceRate: totalFriendships > 0 ? Math.round((accepted / (accepted + declined || 1)) * 100) : 0,
        avgFriendsPerUser: connectedCount > 0 ? Math.round((accepted * 2 / connectedCount) * 10) / 10 : 0,
        onlineNow,
        totalBlocks,
      },
      engagement: {
        pokes: { last24h: pokes24h, last7d: pokes7d, last30d: pokes30d },
        recommendations,
        digestSubscribers,
      },
      achievements: {
        totalUnlocks: achievementUnlocks,
        byCategory: achievementsByCategory,
        recentUnlocks: recentUnlocks.map((u) => ({
          userId: u.userId,
          userName: u.user.name,
          achievementId: u.achievementId,
          unlockedAt: u.unlockedAt,
        })),
      },
      groups: groupStats,
      topConnected,
      friendshipTrends,
    };
  },

  /** Get full social graph data for visualization */
  async getGraphData(filters?: { role?: string; search?: string }): Promise<GraphData> {
    // Build user query filter
    const userWhere: Record<string, unknown> = { status: 'ACTIVE', role: { not: 'TEMP' } };
    if (filters?.role) userWhere.role = filters.role;
    if (filters?.search) {
      userWhere.OR = [
        { name: { contains: filters.search, mode: 'insensitive' } },
        { email: { contains: filters.search, mode: 'insensitive' } },
      ];
    }

    // Batch queries — avoid N+1
    const [users, friendships, presence, achievementCounts, groupMemberships] = await Promise.all([
      prisma.user.findMany({
        where: userWhere,
        select: { id: true, name: true, email: true, role: true },
      }),

      prisma.friendship.findMany({
        select: { userId: true, friendId: true, status: true, acceptedAt: true },
      }),

      prisma.userPresence.findMany({
        where: { isOnline: true },
        select: { userId: true },
      }),

      prisma.userAchievement.groupBy({
        by: ['userId'],
        _count: { id: true },
      }),

      prisma.socialGroupMember.findMany({
        select: { userId: true, group: { select: { name: true } } },
      }),
    ]);

    // Build lookup maps
    const onlineSet = new Set(presence.map((p) => p.userId));
    const achievementMap = new Map(achievementCounts.map((a) => [a.userId, a._count.id]));
    const groupMap = new Map<string, string[]>();
    for (const gm of groupMemberships) {
      const existing = groupMap.get(gm.userId) ?? [];
      existing.push(gm.group.name);
      groupMap.set(gm.userId, existing);
    }

    // Count friends per user (accepted only)
    const friendCountMap = new Map<string, number>();
    for (const f of friendships) {
      if (f.status !== 'accepted') continue;
      friendCountMap.set(f.userId, (friendCountMap.get(f.userId) ?? 0) + 1);
      friendCountMap.set(f.friendId, (friendCountMap.get(f.friendId) ?? 0) + 1);
    }

    const userIdSet = new Set(users.map((u) => u.id));

    const nodes: GraphNode[] = users.map((u) => ({
      id: u.id,
      name: u.name,
      email: u.email,
      role: u.role,
      isOnline: onlineSet.has(u.id),
      friendCount: friendCountMap.get(u.id) ?? 0,
      achievementCount: achievementMap.get(u.id) ?? 0,
      groups: groupMap.get(u.id) ?? [],
    }));

    // Only include edges where both users are in the filtered set
    const edges: GraphEdge[] = friendships
      .filter((f) => userIdSet.has(f.userId) && userIdSet.has(f.friendId))
      .map((f) => ({
        source: f.userId,
        target: f.friendId,
        status: f.status,
        acceptedAt: f.acceptedAt,
      }));

    return { nodes, edges };
  },

  /** Get ego-centric subgraph around a specific user */
  async getEgoGraphData(userId: string, maxHops = 2): Promise<GraphData> {
    const full = await this.getGraphData();

    // BFS from center user
    const visited = new Set<string>();
    const queue: Array<{ id: string; depth: number }> = [{ id: userId, depth: 0 }];
    visited.add(userId);

    // Build adjacency list from accepted edges
    const adjacency = new Map<string, string[]>();
    for (const edge of full.edges) {
      if (edge.status !== 'accepted') continue;
      adjacency.set(edge.source, [...(adjacency.get(edge.source) ?? []), edge.target]);
      adjacency.set(edge.target, [...(adjacency.get(edge.target) ?? []), edge.source]);
    }

    while (queue.length > 0) {
      const current = queue.shift()!;
      if (current.depth >= maxHops) continue;

      const neighbors = adjacency.get(current.id) ?? [];
      for (const neighbor of neighbors) {
        if (!visited.has(neighbor)) {
          visited.add(neighbor);
          queue.push({ id: neighbor, depth: current.depth + 1 });
        }
      }
    }

    const nodeMap = new Map(full.nodes.map((n) => [n.id, n]));
    const nodes = [...visited].map((id) => nodeMap.get(id)).filter(Boolean) as GraphNode[];
    const edges = full.edges.filter((e) => visited.has(e.source) && visited.has(e.target));

    return { nodes, edges };
  },

  /** Get moderation overview data */
  async getModerationData(page = 1, limit = 20): Promise<ModerationData> {
    const [blocks, blockTotal, notifStats, privacyStats, achievStats] = await Promise.all([
      // Paginated blocks
      prisma.userBlock.findMany({
        skip: (page - 1) * limit,
        take: limit,
        orderBy: { createdAt: 'desc' },
        include: {
          user: { select: { name: true, email: true } },
          blockedUser: { select: { name: true, email: true } },
        },
      }),
      prisma.userBlock.count(),

      // Notification stats by type
      prisma.$queryRaw<{ type: string; total: bigint; read: bigint }[]>`
        SELECT type, COUNT(*) AS total,
               SUM(CASE WHEN is_read THEN 1 ELSE 0 END) AS read
        FROM notifications
        GROUP BY type
        ORDER BY total DESC
      `,

      // Privacy adoption
      this._getPrivacyAdoption(),

      // Achievement unlock stats
      this._getAchievementUnlockStats(),
    ]);

    return {
      blocks: {
        items: blocks.map((b) => ({
          id: b.id,
          userId: b.userId,
          userName: b.user.name,
          userEmail: b.user.email,
          blockedUserId: b.blockedUserId,
          blockedUserName: b.blockedUser.name,
          blockedUserEmail: b.blockedUser.email,
          createdAt: b.createdAt,
        })),
        total: blockTotal,
      },
      achievementStats: achievStats,
      notificationStats: notifStats.map((n) => ({
        type: n.type,
        total: Number(n.total),
        read: Number(n.read),
        readRate: Number(n.total) > 0 ? Math.round((Number(n.read) / Number(n.total)) * 100) : 0,
      })),
      privacyAdoption: privacyStats,
    };
  },

  /** Admin force-remove a block */
  async removeBlock(blockId: number) {
    return prisma.userBlock.delete({ where: { id: blockId } });
  },

  /** Admin grant achievement to user */
  async grantAchievement(userId: string, achievementId: string) {
    // Check user exists
    const user = await prisma.user.findUnique({ where: { id: userId }, select: { id: true } });
    if (!user) throw Object.assign(new Error('User not found'), { statusCode: 404 });

    // Check not already unlocked
    const existing = await prisma.userAchievement.findFirst({
      where: { userId, achievementId },
    });
    if (existing) throw Object.assign(new Error('Achievement already unlocked'), { statusCode: 409 });

    return prisma.userAchievement.create({
      data: {
        userId,
        achievementId,
        unlockedAt: new Date(),
        progress: 0,
        notified: true,
      },
    });
  },

  /** Admin revoke achievement from user */
  async revokeAchievement(userId: string, achievementId: string) {
    const existing = await prisma.userAchievement.findFirst({
      where: { userId, achievementId },
    });
    if (!existing) throw Object.assign(new Error('Achievement not found'), { statusCode: 404 });

    return prisma.userAchievement.delete({ where: { id: existing.id } });
  },

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

  /** Get top connected users by accepted friendship count */
  async _getTopConnected(limit: number): Promise<TopUser[]> {
    const results = await prisma.$queryRaw<{ userId: string; count: bigint }[]>`
      SELECT uid AS "userId", COUNT(*) AS count FROM (
        SELECT "user_id" AS uid FROM friendships WHERE status = 'accepted'
        UNION ALL
        SELECT "friend_id" AS uid FROM friendships WHERE status = 'accepted'
      ) friends
      GROUP BY uid
      ORDER BY count DESC
      LIMIT ${limit}
    `;

    if (results.length === 0) return [];

    const users = await prisma.user.findMany({
      where: { id: { in: results.map((r) => r.userId) } },
      select: { id: true, name: true, email: true },
    });
    const userMap = new Map(users.map((u) => [u.id, u]));

    return results.map((r, i) => ({
      rank: i + 1,
      userId: r.userId,
      name: userMap.get(r.userId)?.name ?? null,
      email: userMap.get(r.userId)?.email ?? '',
      friendCount: Number(r.count),
    }));
  },

  /** Get friendship creation trend over last N days */
  async _getFriendshipTrends(days: number): Promise<FriendshipTrend[]> {
    const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
    const results = await prisma.$queryRaw<{ date: Date; count: bigint }[]>`
      SELECT DATE(accepted_at) AS date, COUNT(*) AS count
      FROM friendships
      WHERE status = 'accepted' AND accepted_at >= ${cutoff}
      GROUP BY DATE(accepted_at)
      ORDER BY date ASC
    `;

    return results.map((r) => ({
      date: r.date.toISOString().split('T')[0],
      count: Number(r.count),
    }));
  },

  /** Get group summary stats */
  async _getGroupSummary(): Promise<GroupSummary> {
    const [groups, memberCounts] = await Promise.all([
      prisma.socialGroup.findMany({
        select: { id: true, name: true, type: true, _count: { select: { members: true } } },
      }),
      prisma.socialGroupMember.count(),
    ]);

    const byType: Record<string, number> = {};
    let largest: GroupSummary['largest'] = null;

    for (const g of groups) {
      byType[g.type] = (byType[g.type] ?? 0) + 1;
      if (!largest || g._count.members > largest.memberCount) {
        largest = { id: g.id, name: g.name, memberCount: g._count.members };
      }
    }

    return {
      total: groups.length,
      byType,
      avgMembers: groups.length > 0 ? Math.round((memberCounts / groups.length) * 10) / 10 : 0,
      largest,
    };
  },

  /** Get privacy settings adoption stats */
  async _getPrivacyAdoption(): Promise<PrivacyAdoption[]> {
    const totalUsers = await prisma.user.count({ where: { role: { not: 'TEMP' }, status: 'ACTIVE' } });
    if (totalUsers === 0) return [];

    const privacy = await prisma.privacySettings.findMany();
    const settings = [
      'showOnlineStatus',
      'showCurrentlyWatching',
      'showInFriendActivity',
      'anonymizePublicComments',
      'hidePublicReactions',
      'hidePublicFinishes',
      'allowFriendRequests',
      'closeFriendsOnlyWatching',
    ] as const;

    // Default values for users without explicit privacy settings
    const defaults: Record<string, boolean> = {
      showOnlineStatus: true,
      showCurrentlyWatching: true,
      showInFriendActivity: true,
      anonymizePublicComments: false,
      hidePublicReactions: false,
      hidePublicFinishes: false,
      allowFriendRequests: true,
      closeFriendsOnlyWatching: false,
    };

    return settings.map((setting) => {
      const enabledExplicit = privacy.filter((p) => p[setting] === true).length;
      const disabledExplicit = privacy.filter((p) => p[setting] === false).length;
      // Users without privacy settings use defaults
      const noSettings = totalUsers - privacy.length;
      const enabled = enabledExplicit + (defaults[setting] ? noSettings : 0);
      const disabled = disabledExplicit + (defaults[setting] ? 0 : noSettings);

      return {
        setting,
        enabled,
        disabled,
        enabledPercent: totalUsers > 0 ? Math.round((enabled / totalUsers) * 100) : 0,
      };
    });
  },

  /** Get unlock stats per achievement definition */
  async _getAchievementUnlockStats(): Promise<AchievementStats[]> {
    const totalUsers = await prisma.user.count({ where: { role: { not: 'TEMP' }, status: 'ACTIVE' } });
    const unlocks = await prisma.userAchievement.groupBy({
      by: ['achievementId'],
      _count: { id: true },
    });

    return unlocks.map((u) => ({
      achievementId: u.achievementId,
      totalUnlocks: u._count.id,
      unlockRate: totalUsers > 0 ? Math.round((u._count.id / totalUsers) * 100) : 0,
    }));
  },

  /** Map achievement ID to category */
  _achievementCategory(achievementId: string): string {
    if (achievementId.startsWith('FIRST_SHIFT') || achievementId.startsWith('SHIFT_')) return 'shifts';
    if (achievementId.startsWith('FIRST_CANVASS') || achievementId.startsWith('CANVASS_')) return 'canvass';
    if (achievementId.startsWith('FIRST_CAMPAIGN') || achievementId.startsWith('CAMPAIGN_')) return 'campaigns';
    if (achievementId.startsWith('SOCIAL_') || achievementId.startsWith('TEAM_')) return 'social';
    return 'other';
  },
};
