import { Types } from "mongoose";
import Trip from "../models/TripModel";

/** Minimum ms between location pings per trip (soft rate limit). */
const LOCATION_MIN_INTERVAL_MS = 2000;

export type TripLocationInput = {
  lat: number;
  lng: number;
  heading?: number | null;
  accuracy?: number | null;
  recordedAt?: Date;
};

function buildLastLocation(loc: TripLocationInput) {
  return {
    lat: loc.lat,
    lng: loc.lng,
    heading: loc.heading ?? null,
    accuracy: loc.accuracy ?? null,
    recordedAt: loc.recordedAt ?? new Date(),
  };
}

function mapTrip(trip: InstanceType<typeof Trip>) {
  return {
    _id: trip._id,
    userId: trip.userId,
    vehicleId: trip.vehicleId,
    name: trip.name,
    startPoint: trip.startPoint,
    endPoint: trip.endPoint,
    scheduledDate: trip.scheduledDate,
    lowFuelReminder: trip.lowFuelReminder,
    imageUrl: trip.imageUrl ?? null,
    calculatedDistance: trip.calculatedDistance,
    calculatedMileage: trip.calculatedMileage,
    calculatedGasRequired: trip.calculatedGasRequired,
    calculatedMetrics: trip.calculatedMetrics,
    status: trip.status,
    startedAt: trip.startedAt ?? null,
    endedAt: trip.endedAt ?? null,
    lastLocation: trip.lastLocation ?? null,
    createdAt: trip.createdAt,
    updatedAt: trip.updatedAt,
  };
}

export async function startTrip(
  userId: string,
  tripId: string,
  location?: TripLocationInput,
): Promise<Record<string, unknown>> {
  if (!Types.ObjectId.isValid(tripId)) {
    throw new Error("NOT_FOUND");
  }

  const trip = await Trip.findOne({
    _id: new Types.ObjectId(tripId),
    userId: new Types.ObjectId(userId),
    isDeleted: false,
  }).populate("vehicleId", "name vehicleModel currentMPG");

  if (!trip) {
    throw new Error("NOT_FOUND");
  }

  if (trip.status === "in-progress") {
    throw new Error("ALREADY_IN_PROGRESS");
  }
  if (trip.status === "completed") {
    throw new Error("ALREADY_COMPLETED");
  }
  if (trip.status !== "planned") {
    throw new Error("INVALID_START_STATUS");
  }

  trip.status = "in-progress";
  trip.startedAt = new Date();
  trip.endedAt = null;
  if (location) {
    trip.lastLocation = buildLastLocation(location);
  }
  await trip.save();

  return { trip: mapTrip(trip) };
}

export async function completeTrip(
  userId: string,
  tripId: string,
  location?: TripLocationInput,
): Promise<Record<string, unknown>> {
  if (!Types.ObjectId.isValid(tripId)) {
    throw new Error("NOT_FOUND");
  }

  const trip = await Trip.findOne({
    _id: new Types.ObjectId(tripId),
    userId: new Types.ObjectId(userId),
    isDeleted: false,
  }).populate("vehicleId", "name vehicleModel currentMPG");

  if (!trip) {
    throw new Error("NOT_FOUND");
  }

  if (trip.status === "completed") {
    throw new Error("ALREADY_COMPLETED");
  }
  if (trip.status !== "in-progress") {
    throw new Error("INVALID_COMPLETE_STATUS");
  }

  trip.status = "completed";
  trip.endedAt = new Date();
  if (!trip.startedAt) {
    trip.startedAt = trip.endedAt;
  }
  if (location) {
    trip.lastLocation = buildLastLocation(location);
  }
  await trip.save();

  return { trip: mapTrip(trip) };
}

/**
 * Update last known GPS for an in-progress trip (owner only).
 * Soft rate-limit: rejects if last ping was &lt; 2s ago.
 */
export async function updateTripLocation(
  userId: string,
  tripId: string,
  location: TripLocationInput,
): Promise<Record<string, unknown>> {
  if (!Types.ObjectId.isValid(tripId)) {
    throw new Error("NOT_FOUND");
  }

  const trip = await Trip.findOne({
    _id: new Types.ObjectId(tripId),
    userId: new Types.ObjectId(userId),
    isDeleted: false,
  }).populate("vehicleId", "name vehicleModel currentMPG");

  if (!trip) {
    throw new Error("NOT_FOUND");
  }

  if (trip.status !== "in-progress") {
    throw new Error("NOT_IN_PROGRESS");
  }

  const now = location.recordedAt ?? new Date();
  const prev = trip.lastLocation?.recordedAt;
  if (prev) {
    const elapsed = now.getTime() - new Date(prev).getTime();
    if (elapsed >= 0 && elapsed < LOCATION_MIN_INTERVAL_MS) {
      throw new Error("LOCATION_RATE_LIMIT");
    }
  }

  trip.lastLocation = buildLastLocation({ ...location, recordedAt: now });
  await trip.save();

  return {
    tripId: trip._id,
    status: trip.status,
    lastLocation: trip.lastLocation,
  };
}
