import { Injectable } from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import { Model, Types } from "mongoose";
import { Request, Response } from "express";
import { ResponseService } from "src/common/service/response.service";
import { Performance, PerformanceDocument } from "./schemas/performance.schema";
import { CreatePerformanceDto } from "./dto/create-performance.dto";
import { UpdatePerformanceDto } from "./dto/update-performance.dto";
import {
  Exercise,
  ExerciseDocument,
} from "src/exercise/schemas/exercise.schema";
import {
  isAssistedCategory,
  normalizeAssistedSet,
} from "src/common/utils/assistedWeight.helper";

@Injectable()
export class PerformanceService {
  constructor(
    @InjectModel(Performance.name)
    private performanceModel: Model<PerformanceDocument>,
    @InjectModel(Exercise.name)
    private exerciseModel: Model<ExerciseDocument>,
    private readonly resService: ResponseService,
  ) {}

  // Exercises in an "Assisted Bodyweight"-style category store their
  // assistance as a NEGATIVE assisted_weight (same rule the workout-session
  // flow enforces), no matter whether the client sent it positive or under
  // `weight`. Returns the ids (as strings) of the assisted exercises.
  private async findAssistedExerciseIds(
    exerciseIds: string[],
  ): Promise<Set<string>> {
    const docs = await this.exerciseModel
      .find({ _id: { $in: exerciseIds.map((id) => new Types.ObjectId(id)) } })
      .populate("category")
      .lean();
    return new Set(
      (docs as any[])
        .filter((ex) => isAssistedCategory(ex.category))
        .map((ex) => ex._id.toString()),
    );
  }

  // POST /performance — upsert multiple exercises for a given date, each stored as a separate document
  async create(
    dto: CreatePerformanceDto,
    req: Request,
    res: Response,
    user: any,
  ) {
    try {
      const startDate = new Date(dto.date);
      startDate.setUTCHours(0, 0, 0, 0);
      const endDate = new Date(dto.date);
      endDate.setUTCHours(23, 59, 59, 999);

      const userId = new Types.ObjectId(user._id);

      // Normalize assisted-category exercises before validating/saving.
      const assistedIds = await this.findAssistedExerciseIds(
        dto.exercises.map((ex) => ex.exercise_id),
      );
      const exercises = dto.exercises.map((ex) =>
        assistedIds.has(ex.exercise_id) ? normalizeAssistedSet(ex) : ex,
      );

      // A new record is only allowed when its weight is equal to or higher than
      // the user's current maximum weight (PR) for that exercise. A lower weight
      // is blocked. Reps don't gate this: an equal weight with higher reps is
      // allowed (e.g. 150 kg×1 → 150 kg×3). Only weight-based entries are checked.
      for (const ex of exercises) {
        if (ex.weight == null) continue;
        const [agg] = await this.performanceModel.aggregate([
          {
            $match: {
              userId,
              exercise_id: new Types.ObjectId(ex.exercise_id),
              deleted_at: null,
              weight: { $ne: null },
            },
          },
          { $group: { _id: null, maxWeight: { $max: "$weight" } } },
        ]);
        const maxWeight = agg?.maxWeight;
        if (maxWeight != null && ex.weight < maxWeight) {
          return this.resService.badRequest(
            res,
            "lower_weight",
            req,
          );
        }
      }

      const saved = await Promise.all(
        exercises.map((ex) =>
          this.performanceModel.findOneAndUpdate(
            {
              userId: userId,
              exercise_id: new Types.ObjectId(ex.exercise_id),
              date: { $gte: startDate, $lte: endDate },
              deleted_at: null,
            },
            {
              reps: ex.reps ?? null,
              weight: ex.weight ?? null,
              duration: ex.duration ?? null,
              distance: ex.distance ?? null,
              assisted_weight: ex.assisted_weight ?? null,
              added_weight: ex.added_weight ?? null,
              date: startDate,
            },
            { new: true, upsert: true },
          ),
        ),
      );

      return this.resService.success(
        res,
        "Performance saved successfully",
        req,
        saved,
      );
    } catch (error: any) {
      return this.resService.serverError(
        res,
        "Failed to save performance",
        req,
        error.message || error,
      );
    }
  }

  // GET /performance?date=YYYY-MM-DD — all exercises logged for a specific date
  async getByDate(date: string, req: Request, res: Response, user: any) {
    try {
      const startDate = new Date(date);
      startDate.setUTCHours(0, 0, 0, 0);
      const endDate = new Date(date);
      endDate.setUTCHours(23, 59, 59, 999);

      const data = await this.performanceModel.aggregate([
        {
          $match: {
            userId: new Types.ObjectId(user._id),
            date: { $gte: startDate, $lte: endDate },
            deleted_at: null,
          },
        },
        {
          $lookup: {
            from: "exercises",
            localField: "exercise_id",
            foreignField: "_id",
            as: "exercise",
          },
        },
        { $unwind: { path: "$exercise", preserveNullAndEmptyArrays: true } },
        {
          $lookup: {
            from: "exercisecategories",
            localField: "exercise.category",
            foreignField: "_id",
            as: "exercise.category",
          },
        },
        {
          $unwind: {
            path: "$exercise.category",
            preserveNullAndEmptyArrays: true,
          },
        },
        { $sort: { createdAt: 1 } },
      ]);

      return this.resService.success(res, "success", req, data);
    } catch (error: any) {
      return this.resService.serverError(
        res,
        "Failed to fetch performance",
        req,
        error.message || error,
      );
    }
  }

  // GET /performance/exercise/:exerciseId — full history for one exercise (like Deadlift screen)
  async getExerciseHistory(
    exerciseId: string,
    page: number,
    limit: number,
    res: Response,
    req: Request,
    user: any,
  ) {
    try {
      const skip = (page - 1) * limit;
      const matchStage = {
        exercise_id: new Types.ObjectId(exerciseId),
        userId: new Types.ObjectId(user._id),
        deleted_at: null,
      };

      const [items, total] = await Promise.all([
        this.performanceModel
          .find(matchStage)
          .sort({ date: -1 })
          .skip(skip)
          .limit(limit)
          .lean(),
        this.performanceModel.countDocuments(matchStage),
      ]);

      return this.resService.success(res, "success", req, {
        items,
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
      });
    } catch (error: any) {
      return this.resService.serverError(
        res,
        "Failed to fetch exercise history",
        req,
        error.message || error,
      );
    }
  }

  // GET /performance/personal-records — best set per exercise across all sessions
  async getPersonalRecords(req: Request, res: Response, user: any) {
    try {
      const records = await this.performanceModel.aggregate([
        { $match: { userId: new Types.ObjectId(user._id), deleted_at: null } },
        {
          $group: {
            _id: "$exercise_id",
            max_weight: { $max: "$weight" },
            max_reps: { $max: "$reps" },
            max_duration: { $max: "$duration" },
            max_distance: { $max: "$distance" },
            best_assisted_weight: { $min: "$assisted_weight" }, // least negative = strongest
            max_added_weight: { $max: "$added_weight" },
            last_date: { $max: "$date" },
          },
        },
        {
          $lookup: {
            from: "exercises",
            localField: "_id",
            foreignField: "_id",
            as: "exercise",
          },
        },
        { $unwind: "$exercise" },
        {
          $lookup: {
            from: "exercisecategories",
            localField: "exercise.category",
            foreignField: "_id",
            as: "exercise.category",
          },
        },
        {
          $unwind: {
            path: "$exercise.category",
            preserveNullAndEmptyArrays: true,
          },
        },
        {
          $project: {
            _id: 0,
            exercise_id: "$_id",
            exercise: { _id: 1, name: 1, category: 1 },
            max_weight: 1,
            max_reps: 1,
            max_duration: 1,
            max_distance: 1,
            best_assisted_weight: 1,
            max_added_weight: 1,
            last_date: 1,
          },
        },
        { $sort: { last_date: -1 } },
      ]);

      return this.resService.success(res, "success", req, records);
    } catch (error: any) {
      return this.resService.serverError(
        res,
        "Failed to fetch personal records",
        req,
        error.message || error,
      );
    }
  }

  // GET /performance/personal-records/:exerciseId — PR for a single exercise
  // async getPersonalRecordByExercise(exerciseId: string, req: Request, res: Response, user: any) {
  //   try {
  //     const [record] = await this.performanceModel.aggregate([
  //       {
  //         $match: {
  //           exercise_id: new Types.ObjectId(exerciseId),
  //           deleted_at: null,
  //         },
  //       },
  //       { $unwind: "$sets" },
  //       {
  //         $group: {
  //           _id: "$exercise_id",
  //           max_weight:           { $max: "$sets.weight" },
  //           max_reps:             { $max: "$sets.reps" },
  //           max_duration:         { $max: "$sets.duration" },
  //           max_distance:         { $max: "$sets.distance" },
  //           best_assisted_weight: { $min: "$sets.assisted_weight" },
  //           max_added_weight:     { $max: "$sets.added_weight" },
  //           last_date:            { $max: "$date" },
  //         },
  //       },
  //     ]);
  //     console.log("record", record);

  //     if (!record) return this.resService.notFound(res, "No records found for this exercise", req);
  //     return this.resService.success(res, "success", req, record);
  //   } catch (error: any) {
  //     return this.resService.serverError(res, "Failed to fetch personal record", req, error.message || error);
  //   }
  // }
  async getPersonalRecordHistory(
    exerciseId: string,
    req: Request,
    res: Response,
    user: any,
  ) {
    try {
      const performances = await this.performanceModel.aggregate([
        {
          $match: {
            exercise_id: new Types.ObjectId(exerciseId),
            deleted_at: null,
          },
        },
        {
          $sort: {
            date: 1,
          },
        },
        {
          $project: {
            date: 1,
            weight: 1,
            reps: 1,
            duration: 1,
            distance: 1,
            assisted_weight: 1,
            added_weight: 1,
          },
        },
      ]);

      let currentPR = 0;

      const history = [];

      for (const record of performances) {
        if (
          record.weight !== null &&
          record.weight !== undefined &&
          record.weight > currentPR
        ) {
          currentPR = record.weight;

          history.push({
            date: record.date,
            weight: record.weight,
            reps: record.reps,
          });
        }
      }

      return this.resService.success(
        res,
        "Personal record history fetched successfully",
        req,
        history.reverse(), // latest first
      );
    } catch (error: any) {
      return this.resService.serverError(
        res,
        "Failed to fetch personal record history",
        req,
        error.message || error,
      );
    }
  }

  // PATCH /performance/:id — replace sets for a logged exercise
  async update(
    id: string,
    dto: UpdatePerformanceDto,
    req: Request,
    res: Response,
    user: any,
  ) {
    try {
      let normalizedDate: Date | undefined;
      if (dto.date !== undefined) {
        normalizedDate = new Date(dto.date);
        normalizedDate.setUTCHours(0, 0, 0, 0);
      }

      // Editing a weight can't drop it below the user's current max weight (PR)
      // for that exercise, ignoring this very entry. Equal or higher is allowed.
      if (dto.weight != null) {
        const existing = await this.performanceModel.findOne({
          _id: id,
          deleted_at: null,
        });
        if (existing) {
          const [agg] = await this.performanceModel.aggregate([
            {
              $match: {
                userId: existing.userId,
                exercise_id: existing.exercise_id,
                deleted_at: null,
                weight: { $ne: null },
                _id: { $ne: existing._id },
              },
            },
            { $group: { _id: null, maxWeight: { $max: "$weight" } } },
          ]);
          const maxWeight = agg?.maxWeight;
          if (maxWeight != null && dto.weight < maxWeight) {
            return this.resService.badRequest(res, "lower_weight", req);
          }
        }
      }

      const data = await this.performanceModel.findOneAndUpdate(
        { _id: id, deleted_at: null },
        {
          ...(dto.reps !== undefined && { reps: dto.reps }),
          ...(dto.weight !== undefined && { weight: dto.weight }),
          ...(dto.duration !== undefined && { duration: dto.duration }),
          ...(dto.distance !== undefined && { distance: dto.distance }),
          ...(dto.assisted_weight !== undefined && { assisted_weight: dto.assisted_weight }),
          ...(dto.added_weight !== undefined && { added_weight: dto.added_weight }),
          ...(normalizedDate !== undefined && { date: normalizedDate }),
        },
        { new: true },
      );

      if (!data)
        return this.resService.notFound(res, "Performance log not found", req);
      return this.resService.success(
        res,
        "Performance updated successfully",
        req,
        data,
      );
    } catch (error: any) {
      return this.resService.serverError(
        res,
        "Failed to update performance",
        req,
        error.message || error,
      );
    }
  }

  // DELETE /performance/:id — soft delete one exercise log
  async remove(id: string, req: Request, res: Response, user: any) {
    try {
      const data = await this.performanceModel.findOneAndUpdate(
        { _id: id, deleted_at: null },
        { deleted_at: new Date() },
        { new: true },
      );

      if (!data)
        return this.resService.notFound(res, "Performance log not found", req);
      return this.resService.success(
        res,
        "Performance deleted successfully",
        req,
        data,
      );
    } catch (error: any) {
      return this.resService.serverError(
        res,
        "Failed to delete performance",
        req,
        error.message || error,
      );
    }
  }
}
