import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';import { Model } from 'mongoose';
import { RedisService } from '../redis/redis.service';
import { TripsService } from '../trips/trips.service';
import { User, UserDocument } from '../users/schemas/user.schema';
import { GpsPoint, GpsPointDocument } from '../gps/schemas/gps-point.schema';
import { Trip, TripDocument } from '../trips/schemas/trip.schema';
import { Punch, PunchDocument } from '../punches/schemas/punch.schema';
import { GpsStop, GpsStopDocument } from '../gps/schemas/gps-stop.schema';
import { UserStatus } from '../common/constants/enums';
import { compressRoute } from '../common/utils/geo.util';
import { docId } from '../common/utils/mongo.util';
import { TrackingCoverageService } from '../tracking/tracking-coverage.service';
@Injectable()
export class AdminService {
  constructor(
    @InjectModel(User.name) private readonly userModel: Model<UserDocument>,
    @InjectModel(GpsPoint.name)
    private readonly gpsModel: Model<GpsPointDocument>,
    @InjectModel(Trip.name) private readonly tripModel: Model<TripDocument>,
    @InjectModel(Punch.name) private readonly punchModel: Model<PunchDocument>,
    @InjectModel(GpsStop.name)
    private readonly stopModel: Model<GpsStopDocument>,
    private readonly redis: RedisService,
    private readonly trips: TripsService,
    private readonly trackingCoverage: TrackingCoverageService,
  ) {}
  async liveEmployeeMap() {
    const liveLocations = await this.redis.getAllLiveLocations();
    const users = await this.userModel
      .find({ status: UserStatus.ACTIVE })
      .select(
        'uid fullName employeeCode role presence lastSeenAt',
      )
      .lean()
      .exec();

    const locationMap = new Map(liveLocations.map((l) => [l.userId, l.data]));

    return users.map((u) => ({
      ...u,
      id: u._id.toString(),
      liveLocation: locationMap.get(u._id.toString()) ?? null,
    }));
  }

  async activeTrips() {
    const trips = await this.trips.getActiveTrips();
    return Promise.all(
      trips.map(async (trip) => {
        const tripDoc = await this.tripModel.findOne({ tripId: trip.tripId });
        const lastPoint = tripDoc
          ? await this.gpsModel
              .findOne({ tripId: docId(tripDoc) })
              .sort({ timestamp: -1 })
              .lean()
              .exec()
          : null;
        return {
          ...trip,
          lastGpsPoint: lastPoint
            ? { ...lastPoint, id: lastPoint._id.toString() }
            : null,
        };
      }),
    );
  }

  async routeReplay(tripId: string, compress = true) {
    const trip = await this.tripModel.findOne({ tripId }).lean().exec();
    if (!trip) return null;

    const tripDbId = trip._id.toString();
    const [user, punches, stops, points] = await Promise.all([
      this.userModel
        .findById(trip.userId)
        .select('uid fullName employeeCode')
        .lean()
        .exec(),
      this.punchModel.find({ tripId: tripDbId }).sort({ timestamp: 1 }).lean(),
      this.stopModel.find({ tripId: tripDbId }).lean(),
      this.gpsModel.find({ tripId: tripDbId }).sort({ timestamp: 1 }).lean(),
    ]);

    const route = compress
      ? compressRoute(
          points.map((p) => ({
            latitude: p.latitude,
            longitude: p.longitude,
          })),
          10,
        )
      : points;

    let trackingCoverageReport: Awaited<
      ReturnType<TrackingCoverageService['recomputeAndStore']>
    > | null = null;
    try {
      trackingCoverageReport =
        await this.trackingCoverage.recomputeAndStore(tripId);
    } catch {
      trackingCoverageReport = null;
    }

    return {
      trip: {
        ...trip,
        id: tripDbId,
        user,
        punches,
        stops,
        trackingCoverageSummary: trackingCoverageReport?.summary ?? null,
      },
      trackingCoverage: trackingCoverageReport
        ? {
            requestId: tripId,
            tripId,
            legs: trackingCoverageReport.legs,
            summary: trackingCoverageReport.summary,
          }
        : null,
      route,
      pointCount: points.length,
    };
  }
  async filteredEmployees(filters: {
    role?: string;
    status?: string;
    search?: string;
    managerId?: string;
  }) {
    const query: Record<string, unknown> = {};
    if (filters.role) query.role = filters.role;
    if (filters.status) query.status = filters.status;
    if (filters.managerId) query.reportingManagerId = filters.managerId;
    if (filters.search) {
      const re = new RegExp(
        filters.search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
        'i',
      );
      query.$or = [{ fullName: re }, { employeeCode: re }];
    }

    const users = await this.userModel.find(query).lean().exec();
    const managerIds = [
      ...new Set(
        users.map((u) => u.reportingManagerId).filter(Boolean) as string[],
      ),
    ];
    const managers = managerIds.length
      ? await this.userModel
          .find({ _id: { $in: managerIds } })
          .select('uid fullName')
          .lean()
          .exec()
      : [];
    const managerMap = new Map(managers.map((m) => [m._id.toString(), m]));

    return users.map((u) => ({
      id: u._id.toString(),
      uid: u.uid,
      fullName: u.fullName,
      employeeCode: u.employeeCode,
      role: u.role,
      status: u.status,
      presence: u.presence,
      sittingLocation: u.sittingLocation,
      reportingManager: u.reportingManagerId
        ? managerMap.get(u.reportingManagerId)
        : null,
    }));
  }
}
