import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { TripStatus } from '../common/constants/enums';
import { APP_EVENTS } from '../common/constants/events';
import { Trip, TripDocument } from '../trips/schemas/trip.schema';
import {
  DailySummary,
  DailySummaryDocument,
} from './schemas/daily-summary.schema';
import {
  MonthlySummary,
  MonthlySummaryDocument,
} from './schemas/monthly-summary.schema';

@Injectable()
export class AnalyticsService {
  constructor(
    @InjectModel(Trip.name) private readonly tripModel: Model<TripDocument>,
    @InjectModel(DailySummary.name)
    private readonly dailyModel: Model<DailySummaryDocument>,
    @InjectModel(MonthlySummary.name)
    private readonly monthlyModel: Model<MonthlySummaryDocument>,
  ) {}

  @OnEvent(APP_EVENTS.TRIP_STATUS_CHANGED)
  async onTripComplete(trip: { userId: string; status: string }) {
    if (trip.status !== TripStatus.COMPLETED) return;
    await this.recalculateDaily(trip.userId, new Date());
    await this.recalculateMonthly(trip.userId, new Date());
  }

  async recalculateDaily(userId: string, date: Date) {
    const dayStart = new Date(date);
    dayStart.setHours(0, 0, 0, 0);
    const dayEnd = new Date(dayStart);
    dayEnd.setDate(dayEnd.getDate() + 1);

    const trips = await this.tripModel
      .find({
        userId,
        createdAt: { $gte: dayStart, $lt: dayEnd },
        status: TripStatus.COMPLETED,
      })
      .lean()
      .exec();

    const totals = trips.reduce(
      (acc, t) => ({
        totalDistance: acc.totalDistance + t.totalDistance,
        travelTime: acc.travelTime + t.travelDuration,
        meetingTime: acc.meetingTime + t.meetingDuration,
        idleTime: acc.idleTime + t.idleTime,
        tripsCompleted: acc.tripsCompleted + 1,
        productivityScore: acc.productivityScore + (t.productivityScore ?? 0),
      }),
      {
        totalDistance: 0,
        travelTime: 0,
        meetingTime: 0,
        idleTime: 0,
        tripsCompleted: 0,
        productivityScore: 0,
      },
    );

    const productivityScore =
      trips.length > 0 ? totals.productivityScore / trips.length : null;

    await this.dailyModel.findOneAndUpdate(
      { userId, date: dayStart },
      { ...totals, productivityScore },
      { upsert: true, new: true },
    );
  }

  async recalculateMonthly(userId: string, date: Date) {
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const monthStart = new Date(year, month - 1, 1);
    const monthEnd = new Date(year, month, 1);

    const trips = await this.tripModel
      .find({
        userId,
        createdAt: { $gte: monthStart, $lt: monthEnd },
        status: TripStatus.COMPLETED,
      })
      .lean()
      .exec();

    const totals = trips.reduce(
      (acc, t) => ({
        totalDistance: acc.totalDistance + t.totalDistance,
        travelTime: acc.travelTime + t.travelDuration,
        meetingTime: acc.meetingTime + t.meetingDuration,
        idleTime: acc.idleTime + t.idleTime,
        tripsCompleted: acc.tripsCompleted + 1,
        productivityScore: acc.productivityScore + (t.productivityScore ?? 0),
      }),
      {
        totalDistance: 0,
        travelTime: 0,
        meetingTime: 0,
        idleTime: 0,
        tripsCompleted: 0,
        productivityScore: 0,
      },
    );

    await this.monthlyModel.findOneAndUpdate(
      { userId, year, month },
      {
        ...totals,
        productivityScore:
          trips.length > 0 ? totals.productivityScore / trips.length : null,
      },
      { upsert: true, new: true },
    );
  }

  async userReport(userId: string, from?: string, to?: string) {
    const query: Record<string, unknown> = { userId };
    if (from || to) {
      query.createdAt = {
        ...(from ? { $gte: new Date(from) } : {}),
        ...(to ? { $lte: new Date(to) } : {}),
      };
    }
    const trips = await this.tripModel
      .find(query)
      .sort({ createdAt: -1 })
      .lean()
      .exec();

    return {
      trips: trips.map((t) => ({ ...t, id: t._id.toString() })),
      summary: {
        totalDistance: trips.reduce((s, t) => s + t.totalDistance, 0),
        travelTime: trips.reduce((s, t) => s + t.travelDuration, 0),
        meetingTime: trips.reduce((s, t) => s + t.meetingDuration, 0),
        idleTime: trips.reduce((s, t) => s + t.idleTime, 0),
        avgProductivity:
          trips.length > 0
            ? trips.reduce((s, t) => s + (t.productivityScore ?? 0), 0) /
              trips.length
            : 0,
        avgEfficiency:
          trips.length > 0
            ? trips.reduce((s, t) => s + (t.tripEfficiency ?? 0), 0) /
              trips.length
            : 0,
      },
    };
  }

  async dailySummaries(from?: string, to?: string, userId?: string) {
    const query: Record<string, unknown> = {};
    if (userId) query.userId = userId;
    if (from || to) {
      query.date = {
        ...(from ? { $gte: new Date(from) } : {}),
        ...(to ? { $lte: new Date(to) } : {}),
      };
    }
    const rows = await this.dailyModel.find(query).sort({ date: -1 }).lean().exec();
    return rows.map((r) => ({ ...r, id: r._id.toString() }));
  }

  async monthlySummaries(year?: number, userId?: string) {
    const query: Record<string, unknown> = {};
    if (userId) query.userId = userId;
    if (year) query.year = year;
    const rows = await this.monthlyModel
      .find(query)
      .sort({ year: -1, month: -1 })
      .lean()
      .exec();
    return rows.map((r) => ({ ...r, id: r._id.toString() }));
  }
}
