/**
 * @copyright 2026 NHCarrigan
 * @license Naomi's Public License
 * @author Naomi Carrigan
 */

import type { FastifyRequest } from "fastify";
import {
  ACHIEVEMENTS,
  ACHIEVEMENT_LIST,
  AchievementCategory,
  AchievementDefinition,
  AchievementProgress,
  AuditAction,
  AuditCategory,
  UserAchievementSummary,
} from "@library/shared-types";

import { prisma } from "../lib/prisma";
import { AuditService } from "./audit.service";

export class AchievementService {
  /**
   * Check and award achievements for a user after an action.
   * Returns list of newly earned achievements.
   */
  async checkAchievements(
    userId: string,
    category: AchievementCategory,
    req: FastifyRequest,
  ): Promise<AchievementDefinition[]> {
    const relevantAchievements = ACHIEVEMENT_LIST.filter(
      (ach) => ach.category === category,
    );
    const newlyEarned: AchievementDefinition[] = [];

    for (const achievement of relevantAchievements) {
      const userAchievement = await prisma.userAchievement.findUnique({
        where: {
          userId_achievementKey: {
            userId,
            achievementKey: achievement.key,
          },
        },
      });

      // Skip already earned achievements
      if (userAchievement?.earned) {
        continue;
      }

      // Check if achievement is now earned
      const earned = await this.checkAchievementCondition(userId, achievement);

      if (earned) {
        await this.awardAchievement(userId, achievement, req);
        newlyEarned.push(achievement);
      }
    }

    return newlyEarned;
  }

  /**
   * Award an achievement to a user.
   */
  private async awardAchievement(
    userId: string,
    achievement: AchievementDefinition,
    req: FastifyRequest,
  ): Promise<void> {
    await prisma.userAchievement.upsert({
      where: {
        userId_achievementKey: {
          userId,
          achievementKey: achievement.key,
        },
      },
      create: {
        userId,
        achievementKey: achievement.key,
        progress: 100,
        earned: true,
        earnedAt: new Date(),
      },
      update: {
        earned: true,
        earnedAt: new Date(),
        progress: 100,
      },
    });

    // Update user's achievement points
    await prisma.user.update({
      where: { id: userId },
      data: {
        achievementPoints: {
          increment: achievement.points,
        },
      },
    });

    // Log the achievement unlock
    await AuditService.logFromRequest(req, {
      action: AuditAction.achievementUnlocked,
      category: AuditCategory.content,
      details: `Unlocked achievement: ${achievement.title}`,
    });
  }

  /**
   * Check if a specific achievement condition is met.
   */
  private async checkAchievementCondition(
    userId: string,
    achievement: AchievementDefinition,
  ): Promise<boolean> {
    switch (achievement.category) {
      case AchievementCategory.Suggestion:
        return await this.checkSuggestionAchievement(userId, achievement);
      case AchievementCategory.Like:
        return await this.checkLikeAchievement(userId, achievement);
      case AchievementCategory.Comment:
        return await this.checkCommentAchievement(userId, achievement);
      case AchievementCategory.Engagement:
        return await this.checkEngagementAchievement(userId, achievement);
      case AchievementCategory.Report:
        return await this.checkReportAchievement(userId, achievement);
      default:
        return false;
    }
  }

  /**
   * Check suggestion-based achievements.
   */
  private async checkSuggestionAchievement(
    userId: string,
    achievement: AchievementDefinition,
  ): Promise<boolean> {
    const { requirements } = achievement;

    // Count-based achievements (total suggestions)
    if (
      achievement.key.startsWith("suggestion_first_steps") ||
      achievement.key.startsWith("suggestion_contributor") ||
      achievement.key.startsWith("suggestion_dedicated") ||
      achievement.key.startsWith("suggestion_master") ||
      achievement.key.startsWith("suggestion_legend")
    ) {
      const count = await prisma.suggestion.count({
        where: { userId },
      });
      return count >= (requirements.count ?? 0);
    }

    // Accepted suggestion achievements
    if (achievement.key.startsWith("suggestion_quality")) {
      const count = await prisma.suggestion.count({
        where: {
          userId,
          status: "ACCEPTED",
        },
      });
      return count >= (requirements.count ?? 0);
    }

    // Approved achievement (first accepted)
    if (achievement.key === "suggestion_approved") {
      const count = await prisma.suggestion.count({
        where: {
          userId,
          status: "ACCEPTED",
        },
      });
      return count >= 1;
    }

    // Acceptance rate achievements
    if (achievement.key.startsWith("suggestion_acceptance")) {
      const total = await prisma.suggestion.count({
        where: { userId },
      });
      const accepted = await prisma.suggestion.count({
        where: {
          userId,
          status: "ACCEPTED",
        },
      });

      if (total < (requirements.count ?? 0)) {
        return false;
      }

      const rate = accepted / total;
      return rate >= (requirements.rate ?? 0);
    }

    // Diversity achievement (all media types)
    if (achievement.key === "suggestion_renaissance") {
      const types = await prisma.suggestion.groupBy({
        by: ["entityType"],
        where: {
          userId,
          status: "ACCEPTED",
        },
      });
      return types.length >= 6;
    }

    // Enthusiast (5 in one day)
    if (achievement.key === "suggestion_enthusiast") {
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      const tomorrow = new Date(today);
      tomorrow.setDate(tomorrow.getDate() + 1);

      const count = await prisma.suggestion.count({
        where: {
          userId,
          createdAt: {
            gte: today,
            lt: tomorrow,
          },
        },
      });
      return count >= 5;
    }

    return false;
  }

  /**
   * Check like-based achievements.
   */
  private async checkLikeAchievement(
    userId: string,
    achievement: AchievementDefinition,
  ): Promise<boolean> {
    const { requirements } = achievement;

    // Count-based achievements (total likes)
    if (
      achievement.key.startsWith("like_first") ||
      achievement.key.startsWith("like_enthusiast") ||
      achievement.key.startsWith("like_fan") ||
      achievement.key.startsWith("like_super") ||
      achievement.key.startsWith("like_mega") ||
      achievement.key.startsWith("like_legendary")
    ) {
      const count = await prisma.like.count({
        where: { userId },
      });
      return count >= (requirements.count ?? 0);
    }

    // Media-specific achievements
    if (achievement.key === "like_book_lover") {
      const count = await prisma.like.count({
        where: {
          userId,
          entityType: "book",
        },
      });
      return count >= 50;
    }

    if (achievement.key === "like_gamer") {
      const count = await prisma.like.count({
        where: {
          userId,
          entityType: "game",
        },
      });
      return count >= 50;
    }

    if (achievement.key === "like_cinephile") {
      const count = await prisma.like.count({
        where: {
          userId,
          entityType: "show",
        },
      });
      return count >= 50;
    }

    if (achievement.key === "like_music") {
      const count = await prisma.like.count({
        where: {
          userId,
          entityType: "music",
        },
      });
      return count >= 50;
    }

    // Diversity achievement
    if (achievement.key === "like_diverse") {
      const types = await prisma.like.groupBy({
        by: ["entityType"],
        where: { userId },
      });
      return types.length >= 6;
    }

    // Binge liker (20+ in one day)
    if (achievement.key === "like_binge") {
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      const tomorrow = new Date(today);
      tomorrow.setDate(tomorrow.getDate() + 1);

      const count = await prisma.like.count({
        where: {
          userId,
          createdAt: {
            gte: today,
            lt: tomorrow,
          },
        },
      });
      return count >= 20;
    }

    return false;
  }

  /**
   * Check comment-based achievements.
   */
  private async checkCommentAchievement(
    userId: string,
    achievement: AchievementDefinition,
  ): Promise<boolean> {
    const { requirements } = achievement;

    // Count-based achievements (total comments)
    if (
      achievement.key.startsWith("comment_first") ||
      achievement.key.startsWith("comment_reviewer") ||
      achievement.key.startsWith("comment_critic") ||
      achievement.key.startsWith("comment_expert") ||
      achievement.key.startsWith("comment_master") ||
      achievement.key.startsWith("comment_legend")
    ) {
      const count = await prisma.comment.count({
        where: { userId },
      });
      return count >= (requirements.count ?? 0);
    }

    // Length-based achievements
    if (achievement.key === "comment_detailed") {
      const count = await prisma.comment.count({
        where: {
          userId,
          content: {
            // MongoDB doesn't have a direct length check, so we'll fetch and check
          },
        },
      });

      // Fetch all comments and check length
      const comments = await prisma.comment.findMany({
        where: { userId },
        select: { content: true },
      });

      const longComments = comments.filter((c) => c.content.length >= 500);
      return longComments.length >= 10;
    }

    if (achievement.key === "comment_essay") {
      const comments = await prisma.comment.findMany({
        where: { userId },
        select: { content: true },
      });

      const longComments = comments.filter((c) => c.content.length >= 1000);
      return longComments.length >= 5;
    }

    if (achievement.key === "comment_novel") {
      const comments = await prisma.comment.findMany({
        where: { userId },
        select: { content: true },
      });

      const longComments = comments.filter((c) => c.content.length >= 2000);
      return longComments.length >= 3;
    }

    // Thoughtful reviewer (50 different items)
    if (achievement.key === "comment_thoughtful") {
      // Count unique entity IDs
      const comments = await prisma.comment.findMany({
        where: { userId },
        select: {
          bookId: true,
          gameId: true,
          showId: true,
          mangaId: true,
          musicId: true,
          artId: true,
        },
      });

      const uniqueItems = new Set<string>();
      comments.forEach((c) => {
        const entityId =
          c.bookId ??
          c.gameId ??
          c.showId ??
          c.mangaId ??
          c.musicId ??
          c.artId;
        if (entityId) {
          uniqueItems.add(entityId);
        }
      });

      return uniqueItems.size >= 50;
    }

    // Diversity achievement
    if (achievement.key === "comment_diverse") {
      const comments = await prisma.comment.findMany({
        where: { userId },
        select: {
          bookId: true,
          gameId: true,
          showId: true,
          mangaId: true,
          musicId: true,
          artId: true,
        },
      });

      const types = new Set<string>();
      comments.forEach((c) => {
        if (c.bookId) {
          types.add("book");
        }
        if (c.gameId) {
          types.add("game");
        }
        if (c.showId) {
          types.add("show");
        }
        if (c.mangaId) {
          types.add("manga");
        }
        if (c.musicId) {
          types.add("music");
        }
        if (c.artId) {
          types.add("art");
        }
      });

      return types.size >= 6;
    }

    return false;
  }

  /**
   * Check engagement-based achievements.
   */
  private async checkEngagementAchievement(
    userId: string,
    achievement: AchievementDefinition,
  ): Promise<boolean> {
    const user = await prisma.user.findUnique({
      where: { id: userId },
      select: {
        currentStreak: true,
        createdAt: true,
      },
    });

    if (!user) {
      return false;
    }

    // Welcome achievement
    if (achievement.key === "engagement_welcome") {
      return true; // Awarded on first login
    }

    // Streak-based achievements
    if (achievement.key.startsWith("engagement_streak")) {
      return user.currentStreak >= (achievement.requirements.streak ?? 0);
    }

    // Triple threat (suggestion, like, comment in same day)
    if (achievement.key === "engagement_triple_threat") {
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      const tomorrow = new Date(today);
      tomorrow.setDate(tomorrow.getDate() + 1);

      const [hasSuggestion, hasLike, hasComment] = await Promise.all([
        prisma.suggestion.count({
          where: {
            userId,
            createdAt: { gte: today, lt: tomorrow },
          },
        }),
        prisma.like.count({
          where: {
            userId,
            createdAt: { gte: today, lt: tomorrow },
          },
        }),
        prisma.comment.count({
          where: {
            userId,
            createdAt: { gte: today, lt: tomorrow },
          },
        }),
      ]);

      return hasSuggestion > 0 && hasLike > 0 && hasComment > 0;
    }

    // Power user (triple threat 10 times) - would need special tracking
    // Early adopter achievements
    if (
      achievement.key === "engagement_early_adopter" ||
      achievement.key === "engagement_founding_100" ||
      achievement.key === "engagement_founding_1000"
    ) {
      const userRank = await prisma.user.count({
        where: {
          createdAt: {
            lt: user.createdAt,
          },
        },
      });

      if (achievement.key === "engagement_early_adopter") {
        return userRank < 10;
      }
      if (achievement.key === "engagement_founding_100") {
        return userRank < 100;
      }
      if (achievement.key === "engagement_founding_1000") {
        return userRank < 1000;
      }
    }

    // Account age achievements
    if (achievement.key.startsWith("engagement_veteran")) {
      const daysSinceCreation = Math.floor(
        (Date.now() - user.createdAt.getTime()) / (1000 * 60 * 60 * 24),
      );
      return daysSinceCreation >= (achievement.requirements.dayRange ?? 0);
    }

    return false;
  }

  /**
   * Check report-based achievements.
   */
  private async checkReportAchievement(
    userId: string,
    achievement: AchievementDefinition,
  ): Promise<boolean> {
    const { requirements } = achievement;

    // Count ACTION_TAKEN reports
    if (
      achievement.key.startsWith("report_watchful") ||
      achievement.key.startsWith("report_guardian") ||
      achievement.key.startsWith("report_protector") ||
      achievement.key.startsWith("report_vigilant")
    ) {
      const profileReports = await prisma.profileReport.count({
        where: {
          reporterId: userId,
          status: "ACTION_TAKEN",
        },
      });

      const commentReports = await prisma.commentReport.count({
        where: {
          reporterId: userId,
          status: "ACTION_TAKEN",
        },
      });

      return profileReports + commentReports >= (requirements.count ?? 0);
    }

    // Accuracy achievements
    if (achievement.key.startsWith("report_accuracy")) {
      const totalProfileReports = await prisma.profileReport.count({
        where: {
          reporterId: userId,
          status: {
            not: "PENDING",
          },
        },
      });

      const totalCommentReports = await prisma.commentReport.count({
        where: {
          reporterId: userId,
          status: {
            not: "PENDING",
          },
        },
      });

      const total = totalProfileReports + totalCommentReports;

      if (total < (requirements.count ?? 10)) {
        return false;
      }

      const actionTakenProfile = await prisma.profileReport.count({
        where: {
          reporterId: userId,
          status: "ACTION_TAKEN",
        },
      });

      const actionTakenComment = await prisma.commentReport.count({
        where: {
          reporterId: userId,
          status: "ACTION_TAKEN",
        },
      });

      const actionTaken = actionTakenProfile + actionTakenComment;
      const rate = actionTaken / total;

      return rate >= (requirements.rate ?? 0);
    }

    // Volume achievement (total reports)
    if (achievement.key === "report_volume") {
      const profileReports = await prisma.profileReport.count({
        where: { reporterId: userId },
      });

      const commentReports = await prisma.commentReport.count({
        where: { reporterId: userId },
      });

      return profileReports + commentReports >= 50;
    }

    return false;
  }

  /**
   * Get a user's achievement progress and summary.
   */
  async getUserAchievementSummary(
    userId: string,
  ): Promise<UserAchievementSummary> {
    const userAchievements = await prisma.userAchievement.findMany({
      where: { userId },
      orderBy: { earnedAt: "desc" },
    });

    const earnedAchievements = userAchievements.filter((ua) => ua.earned);
    const totalPoints = earnedAchievements.reduce((sum, ua) => {
      const definition = ACHIEVEMENTS[ua.achievementKey];
      return sum + (definition?.points ?? 0);
    }, 0);

    const recentAchievements: AchievementProgress[] = earnedAchievements
      .slice(0, 5)
      .map((ua) => ({
        definition: ACHIEVEMENTS[ua.achievementKey],
        progress: ua.progress,
        earned: ua.earned,
        earnedAt: ua.earnedAt ?? undefined,
      }));

    // Progress by category
    const progressByCategory = Object.values(AchievementCategory).map(
      (category) => {
        const categoryAchievements = ACHIEVEMENT_LIST.filter(
          (a) => a.category === category,
        );
        const earned = earnedAchievements.filter(
          (ua) => ACHIEVEMENTS[ua.achievementKey]?.category === category,
        ).length;

        return {
          category,
          earned,
          total: categoryAchievements.length,
        };
      },
    );

    return {
      totalPoints,
      totalEarned: earnedAchievements.length,
      recentAchievements,
      progressByCategory,
    };
  }

  /**
   * Get all achievements with progress for a user.
   */
  async getUserAchievementProgress(
    userId: string,
  ): Promise<AchievementProgress[]> {
    const userAchievements = await prisma.userAchievement.findMany({
      where: { userId },
    });

    const progressMap = new Map(
      userAchievements.map((ua) => [ua.achievementKey, ua]),
    );

    return ACHIEVEMENT_LIST.map((definition) => {
      const userAchievement = progressMap.get(definition.key);
      return {
        definition,
        progress: userAchievement?.progress ?? 0,
        earned: userAchievement?.earned ?? false,
        earnedAt: userAchievement?.earnedAt ?? undefined,
      };
    });
  }

  /**
   * Update login streak for a user.
   */
  async updateLoginStreak(userId: string): Promise<void> {
    const user = await prisma.user.findUnique({
      where: { id: userId },
      select: {
        currentStreak: true,
        lastStreakCheck: true,
      },
    });

    if (!user) {
      return;
    }

    const now = new Date();
    const yesterday = new Date(now);
    yesterday.setDate(yesterday.getDate() - 1);
    yesterday.setHours(0, 0, 0, 0);

    const lastCheck = user.lastStreakCheck;
    const isConsecutive =
      lastCheck &&
      lastCheck >= yesterday &&
      lastCheck < new Date(now.setHours(0, 0, 0, 0));

    await prisma.user.update({
      where: { id: userId },
      data: {
        currentStreak: isConsecutive ? user.currentStreak + 1 : 1,
        lastStreakCheck: new Date(),
      },
    });
  }
}
