import { Injectable } from "@nestjs/common";
import { CreateMacroLogDto } from "./dto/create-macro.dto";
import { UpdateMacroDto } from "./dto/update-macro.dto";
import { SetTargetMacroDto } from "./dto/set-target-macro.dto";
import { InjectModel } from "@nestjs/mongoose";
import { ResponseService } from "src/common/service/response.service";
import { MacroLog, MacroLogDocument } from "./schemas/macro.schema";
import {
  MacroTarget,
  MacroTargetDocument,
} from "./schemas/target-macro.schema";
import { Request, Response } from "express";
import { Model, Types } from "mongoose";

@Injectable()
export class MacrosService {
  constructor(
    @InjectModel(MacroLog.name)
    private macroLogModel: Model<MacroLogDocument>,
    @InjectModel(MacroTarget.name)
    private macroTargetModel: Model<MacroTargetDocument>,
    private readonly resService: ResponseService,
  ) {}

  async create(dto: CreateMacroLogDto, req: Request, res: Response, user: any) {
    try {
      const userId = new Types.ObjectId(user._id);

      // canonical UTC midnight for the day — same value on write and lookup
      const day = new Date(dto.date);
      day.setUTCHours(0, 0, 0, 0);

      // accumulate onto the existing entry for the day instead of replacing it
      const data = await this.macroLogModel.findOneAndUpdate(
        { userId, date: day, deleted_at: null },
        {
          $inc: {
            calories: dto.calories ?? 0,
            protein: dto.protein ?? 0,
            carbohydrates: dto.carbohydrates ?? 0,
            fats: dto.fats ?? 0,
          },
          $setOnInsert: { userId, date: day, deleted_at: null },
        },
        { new: true, upsert: true },
      );

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

  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.macroLogModel.findOne({
        userId: user._id,
        date: { $gte: startDate, $lte: endDate },
        deleted_at: null,
      });

      return this.resService.success(
        res,
        "success",
        req,
        data || {
          calories: 0,
          protein: 0,
          carbohydrates: 0,
          fats: 0,
        },
      );
    } catch (error: any) {
      return this.resService.serverError(
        res,
        "Failed to fetch macros",
        req,
        error.message || error,
      );
    }
  }

  async getWeekly(weekStart: string, req: Request, res: Response, user: any) {
    try {
      const start = new Date(weekStart);
      start.setUTCHours(0, 0, 0, 0);

      const end = new Date(start);
      end.setUTCDate(end.getUTCDate() + 6);
      end.setUTCHours(23, 59, 59, 999);

      const logs = await this.macroLogModel
        .find({
          userId: user._id,
          date: { $gte: start, $lte: end },
          deleted_at: null,
        })
        .sort({ date: 1 });

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

  async update(
    id: string,
    dto: UpdateMacroDto,
    req: Request,
    res: Response,
    user: any,
  ) {
    try {
      const data = await this.macroLogModel.findOneAndUpdate(
        { _id: id, userId: user._id, deleted_at: null },
        dto,
        { new: true },
      );

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

  async remove(id: string, req: Request, res: Response, user: any) {
    try {
      const data = await this.macroLogModel.findOneAndUpdate(
        { _id: id, userId: user._id, deleted_at: null },
        { deleted_at: new Date() },
        { new: true },
      );

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

  // async getOverview(date: string, req: Request, res: Response, user: any) {
  //   try {
  //     const startDate = new Date(date);
  //     startDate.setHours(0, 0, 0, 0);

  //     const endDate = new Date(date);
  //     endDate.setHours(23, 59, 59, 999);

  //     const today = new Date();
  //     today.setHours(0, 0, 0, 0);
  //     const isPastDate = startDate < today;

  //     const [target, log] = await Promise.all([
  //       // find the most recent target whose effectiveDate is on or before the requested date
  //       this.macroTargetModel
  //         .findOne({ userId: user._id, effectiveDate: { $lte: endDate }, deleted_at: null })
  //         .sort({ effectiveDate: -1 }),
  //       this.macroLogModel.findOne({
  //         userId: user._id,
  //         date: { $gte: startDate, $lte: endDate },
  //         deleted_at: null,
  //       }),
  //     ]);

  //     // Past date with no log → no activity on that day, return all zeros
  //     if (isPastDate && !log) {
  //       return this.resService.success(res, "success", req, {
  //         date,
  //         calories:      { target: 0, consumed: 0, remaining: 0, fulfillmentPercent: 0 },
  //         protein:       { target: 0, consumed: 0, remaining: 0, fulfillmentPercent: 0 },
  //         carbohydrates: { target: 0, consumed: 0, remaining: 0, fulfillmentPercent: 0 },
  //         fats:          { target: 0, consumed: 0, remaining: 0, fulfillmentPercent: 0 },
  //       });
  //     }

  //     const targets = {
  //       calories:      target?.calories      ?? 0,
  //       protein:       target?.protein       ?? 0,
  //       carbohydrates: target?.carbohydrates ?? 0,
  //       fats:          target?.fats          ?? 0,
  //     };

  //     const consumed = {
  //       calories:      log?.calories      ?? 0,
  //       protein:       log?.protein       ?? 0,
  //       carbohydrates: log?.carbohydrates ?? 0,
  //       fats:          log?.fats          ?? 0,
  //     };

  //     const calcFulfillment = (c: number, t: number) =>
  //       t > 0 ? Math.min(Math.round((c / t) * 100), 100) : 0;

  //     const overview = {
  //       date,
  //       calories: {
  //         target: targets.calories,
  //         consumed: consumed.calories,
  //         remaining: Math.max(targets.calories - consumed.calories, 0),
  //         fulfillmentPercent: calcFulfillment(consumed.calories, targets.calories),
  //       },
  //       protein: {
  //         target: targets.protein,
  //         consumed: consumed.protein,
  //         remaining: Math.max(targets.protein - consumed.protein, 0),
  //         fulfillmentPercent: calcFulfillment(consumed.protein, targets.protein),
  //       },
  //       carbohydrates: {
  //         target: targets.carbohydrates,
  //         consumed: consumed.carbohydrates,
  //         remaining: Math.max(targets.carbohydrates - consumed.carbohydrates, 0),
  //         fulfillmentPercent: calcFulfillment(consumed.carbohydrates, targets.carbohydrates),
  //       },
  //       fats: {
  //         target: targets.fats,
  //         consumed: consumed.fats,
  //         remaining: Math.max(targets.fats - consumed.fats, 0),
  //         fulfillmentPercent: calcFulfillment(consumed.fats, targets.fats),
  //       },
  //     };

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

  async getOverview(date: string, req: Request, res: Response, user: any) {
    try {
      const startDate = new Date(date);
      startDate.setUTCHours(0, 0, 0, 0);
    const userId = new Types.ObjectId(user._id);

      const endDate = new Date(date);
      endDate.setUTCHours(23, 59, 59, 999);

      const today = new Date();
      today.setUTCHours(0, 0, 0, 0);

      const isPastDate = startDate < today;
      const isFutureDate = startDate > today;

      let targetPromise;

      if (isFutureDate) {
        // For future dates, always return latest target set by user
        targetPromise = this.macroTargetModel
          .findOne({
            userId: userId,
            deleted_at: null,
          })
          .sort({ effectiveDate: -1 });
      } else {
        // For today and past dates, return target active on that date
        targetPromise = this.macroTargetModel
          .findOne({
            userId: userId,
            effectiveDate: { $lte: endDate },
            deleted_at: null,
          })
          .sort({ effectiveDate: -1 });
      }

      const [target, log] = await Promise.all([
        targetPromise,
        this.macroLogModel.findOne({
          userId: userId,
          date: { $gte: startDate, $lte: endDate },
          deleted_at: null,
        }),
      ]);

      // Past date with no log → return all zeros
      if (isPastDate && !log) {
        return this.resService.success(res, "success", req, {
          date,
          calories: {
            target: 0,
            consumed: 0,
            remaining: 0,
            fulfillmentPercent: 0,
          },
          protein: {
            target: 0,
            consumed: 0,
            remaining: 0,
            fulfillmentPercent: 0,
          },
          carbohydrates: {
            target: 0,
            consumed: 0,
            remaining: 0,
            fulfillmentPercent: 0,
          },
          fats: { target: 0, consumed: 0, remaining: 0, fulfillmentPercent: 0 },
        });
      }

      const targets = {
        calories: target?.calories ?? 0,
        protein: target?.protein ?? 0,
        carbohydrates: target?.carbohydrates ?? 0,
        fats: target?.fats ?? 0,
      };

      const consumed = {
        calories: log?.calories ?? 0,
        protein: log?.protein ?? 0,
        carbohydrates: log?.carbohydrates ?? 0,
        fats: log?.fats ?? 0,
      };

      const calcFulfillment = (c: number, t: number) =>
        t > 0 ? Math.round((c / t) * 100) : 0;

      const overview = {
        date,
        calories: {
          target: targets.calories,
          consumed: consumed.calories,
          remaining: Math.max(targets.calories - consumed.calories, 0),
          fulfillmentPercent: calcFulfillment(
            consumed.calories,
            targets.calories,
          ),
        },
        protein: {
          target: targets.protein,
          consumed: consumed.protein,
          remaining: Math.max(targets.protein - consumed.protein, 0),
          fulfillmentPercent: calcFulfillment(
            consumed.protein,
            targets.protein,
          ),
        },
        carbohydrates: {
          target: targets.carbohydrates,
          consumed: consumed.carbohydrates,
          remaining: Math.max(
            targets.carbohydrates - consumed.carbohydrates,
            0,
          ),
          fulfillmentPercent: calcFulfillment(
            consumed.carbohydrates,
            targets.carbohydrates,
          ),
        },
        fats: {
          target: targets.fats,
          consumed: consumed.fats,
          remaining: Math.max(targets.fats - consumed.fats, 0),
          fulfillmentPercent: calcFulfillment(consumed.fats, targets.fats),
        },
      };

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

  async setTarget(
    dto: SetTargetMacroDto,
    req: Request,
    res: Response,
    user: any,
  ) {
    try {
      const userId = new Types.ObjectId(user._id);
      // const effectiveDate = new Date(
      //   dto.effectiveDate ?? new Date().toISOString().split("T")[0],
      // );
       const effectiveDate = new Date(
        dto.effectiveDate ?? new Date().toISOString().split("T")[0],
      );
      effectiveDate.setUTCHours(0, 0, 0, 0);

      const { effectiveDate: _ignored, ...macroFields } = dto;

      const data = await this.macroTargetModel.findOneAndUpdate(
        { userId, effectiveDate },
        { ...macroFields, userId, effectiveDate, deleted_at: null },
        { new: true, upsert: true },
      );

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

  async getTarget(req: Request, res: Response, user: any) {
    try {
      const userId = new Types.ObjectId(user._id);
      const today = new Date();
      today.setUTCHours(23, 59, 59, 999);

      // returns the most recently set target that is active on or before today
      const data = await this.macroTargetModel
        .findOne({
          userId,
          effectiveDate: { $lte: today },
          deleted_at: null,
        })
        .sort({ effectiveDate: -1 });

      return this.resService.success(
        res,
        "success",
        req,
        data || {
          calories: 0,
          protein: 0,
          carbohydrates: 0,
          fats: 0,
        },
      );
    } catch (error: any) {
      return this.resService.serverError(
        res,
        "Failed to fetch macro targets",
        req,
        error.message || error,
      );
    }
  }
}
