import { BadRequestException } from '@nestjs/common';
import {
  FuelType,
  TripStatus,
  VehicleType,
} from '../common/constants/enums';
import { CreateTripDto } from './dto/trip.dto';
import { GpsBatchDto } from '../gps/dto/gps.dto';
import {
  ArrivalPunchDto,
  DeparturePunchDto,
  MeetingEndPunchDto,
  MeetingStartPunchDto,
} from '../punches/dto/punch.dto';
import { AddNextClientDto } from './dto/add-next-client.dto';
import { assertFuelTypeForVehicle } from '../settings/fuel-rates.util';
import { hasPunch, isLegComplete, legPunchesLookLeaked } from './trip-leg.util';

const VEHICLE_MAP: Record<string, VehicleType> = {
  car: VehicleType.CAR,
  bike: VehicleType.BIKE,
  scooter: VehicleType.SCOOTER,
  other: VehicleType.OTHER,
};

const FUEL_MAP: Record<string, FuelType> = {
  petrol: FuelType.PETROL,
  diesel: FuelType.DIESEL,
  cng: FuelType.CNG,
  electric: FuelType.ELECTRIC,
  other: FuelType.OTHER,
};

export function parseVehicleType(value?: string): VehicleType | undefined {
  if (!value?.trim()) return undefined;
  const key = value.trim().toLowerCase();
  const mapped = VEHICLE_MAP[key];
  if (mapped) return mapped;
  const upper = value.trim().toUpperCase();
  if (Object.values(VehicleType).includes(upper as VehicleType)) {
    return upper as VehicleType;
  }
  throw new BadRequestException(`Invalid vehicleType: ${value}`);
}

export function parseFuelType(value?: string): FuelType | undefined {
  if (!value?.trim()) return undefined;
  const key = value.trim().toLowerCase();
  const mapped = FUEL_MAP[key];
  if (mapped) return mapped;
  const upper = value.trim().toUpperCase();
  if (Object.values(FuelType).includes(upper as FuelType)) {
    return upper as FuelType;
  }
  throw new BadRequestException(`Invalid fuelType: ${value}`);
}

export function toClientVehicleType(value?: VehicleType): string | null {
  if (!value) return null;
  return value.toLowerCase();
}

export function toClientFuelType(value?: FuelType): string | null {
  if (!value) return null;
  return value.toLowerCase();
}

export function toClientTripStatus(status: TripStatus): string {
  return status.toLowerCase();
}

/** Flutter legacy status values used to drive UI (Leg 1, Mark Arrival, etc.). */
export function toLegacyTripStatus(status: TripStatus): string {
  switch (status) {
    case TripStatus.CREATED:
      return 'created';
    case TripStatus.STARTED:
    case TripStatus.TRAVELLING:
      return 'departed';
    case TripStatus.ARRIVED:
      return 'arrived';
    case TripStatus.MEETING_STARTED:
      return 'meeting_started';
    case TripStatus.MEETING_COMPLETED:
      return 'meeting_completed';
    case TripStatus.RETURN_TRIP:
      return 'return_trip';
    case TripStatus.COMPLETED:
      return 'completed';
    case TripStatus.CANCELLED:
      return 'cancelled';
    default:
      return String(status).toLowerCase();
  }
}

export function isActiveTripStatus(status: TripStatus): boolean {
  return status !== TripStatus.COMPLETED && status !== TripStatus.CANCELLED;
}

export function canMarkArrival(status: TripStatus): boolean {
  return status === TripStatus.STARTED || status === TripStatus.TRAVELLING;
}

export function currentLegNumber(status: TripStatus): number {
  return status === TripStatus.RETURN_TRIP ? 2 : 1;
}

export function toClientPunch(punch: Record<string, unknown>) {
  const type = String(punch.type ?? '').toLowerCase();
  return {
    id: punch.id ?? punch._id?.toString?.() ?? null,
    type,
    punchType: type,
    timestamp: punch.timestamp ?? null,
    latitude: punch.latitude ?? null,
    longitude: punch.longitude ?? null,
    lat: punch.latitude ?? null,
    lng: punch.longitude ?? null,
    address: punch.address ?? null,
    batteryPercent: punch.batteryPercent ?? null,
    gpsAccuracy: punch.gpsAccuracy ?? null,
    speed: punch.speed ?? null,
  };
}

export function toClientRoutePoint(point: Record<string, unknown>) {
  return {
    id: point.id ?? point._id?.toString?.() ?? null,
    latitude: point.latitude,
    longitude: point.longitude,
    lat: point.latitude,
    lng: point.longitude,
    timestamp: point.timestamp ?? null,
    accuracy: point.accuracy ?? null,
    speed: point.speed ?? null,
  };
}

function sanitizePersistedLegPunches(
  legs: Record<string, unknown>[],
): Record<string, unknown>[] {
  const cleared = (leg: Record<string, unknown>) => ({
    ...leg,
    departurePunch: null,
    arrivalPunch: null,
    meetingStartPunch: null,
    meetingEndPunch: null,
  });

  let updated = legs.map((leg) => ({ ...leg }));
  for (let i = 1; i < updated.length; i++) {
    const leg = updated[i] as Record<string, unknown>;
    if (leg.isReturnLeg === true) continue;
    if (
      legPunchesLookLeaked(
        leg,
        updated[i - 1] as Record<string, unknown>,
      )
    ) {
      updated[i] = cleared(updated[i]);
    }
  }

  const firstIncomplete = updated.findIndex(
    (leg) => !isLegComplete(leg as Record<string, unknown>),
  );
  if (firstIncomplete < 0) return updated;

  return updated.map((leg, index) => {
    if (index < firstIncomplete) return leg;
    if (index === firstIncomplete) {
      return hasPunch(leg, 'departurePunch') ? leg : cleared(leg);
    }
    return cleared(leg);
  });
}

export function buildLegs(
  trip: Record<string, unknown>,
  punches: ReturnType<typeof toClientPunch>[],
) {
  const persisted = trip.tripLegs;
  if (Array.isArray(persisted) && persisted.length > 0) {
    const sanitized = sanitizePersistedLegPunches(
      persisted as Record<string, unknown>[],
    );
    return sanitized.map((leg, index) => {
      const map = leg as Record<string, unknown>;
      return {
        legId: map.legId ?? `leg_${index + 1}`,
        sequence: map.sequence ?? index + 1,
        legNumber: map.sequence ?? index + 1,
        label: `Leg ${map.sequence ?? index + 1}`,
        fromLocation: map.fromLocation ?? null,
        toLocation: map.toLocation ?? null,
        clientName: map.clientName ?? null,
        purpose: map.purpose ?? null,
        clientOfficeAddress: map.clientOfficeAddress ?? map.toLocation ?? null,
        isReturnLeg: map.isReturnLeg === true,
        departurePunch: map.departurePunch ?? null,
        arrivalPunch: map.arrivalPunch ?? null,
        meetingStartPunch: map.meetingStartPunch ?? null,
        meetingEndPunch: map.meetingEndPunch ?? null,
        status: isLegComplete(map as Record<string, unknown>)
          ? 'completed'
          : 'active',
      };
    });
  }

  const status = trip.status as TripStatus;
  const leg1Status =
    status === TripStatus.RETURN_TRIP ||
    status === TripStatus.COMPLETED ||
    status === TripStatus.ARRIVED ||
    status === TripStatus.MEETING_STARTED ||
    status === TripStatus.MEETING_COMPLETED
      ? 'completed'
      : toLegacyTripStatus(status);

  const legs = [
    {
      legNumber: 1,
      label: 'Leg 1',
      fromLocation: trip.fromLocation ?? null,
      toLocation: trip.toLocation ?? null,
      status: leg1Status,
      departurePunch: punches.find((p) => p.type === 'departure') ?? null,
      arrivalPunch: punches.find((p) => p.type === 'arrival') ?? null,
    },
  ];

  if (
    status === TripStatus.RETURN_TRIP ||
    status === TripStatus.COMPLETED
  ) {
    legs.push({
      legNumber: 2,
      label: 'Leg 2',
      fromLocation: trip.toLocation ?? null,
      toLocation: trip.fromLocation ?? null,
      status:
        status === TripStatus.COMPLETED ? 'completed' : toLegacyTripStatus(status),
      departurePunch: null,
      arrivalPunch: null,
    });
  }

  return legs;
}

function resolveActiveLegIndex(
  trip: Record<string, unknown>,
  tripLegs: ReturnType<typeof buildLegs>,
): number {
  if (tripLegs.length > 1) {
    const incomplete = tripLegs.findIndex((leg) => leg.status !== 'completed');
    return incomplete === -1
      ? Math.max(0, tripLegs.length - 1)
      : incomplete;
  }
  const stored = trip.currentLegIndex;
  if (typeof stored === 'number' && stored >= 0) {
    return Math.min(stored, Math.max(0, tripLegs.length - 1));
  }
  const incomplete = tripLegs.findIndex((leg) => leg.status !== 'completed');
  return incomplete === -1 ? Math.max(0, tripLegs.length - 1) : incomplete;
}

export function toClientTripSummary(trip: Record<string, unknown>) {
  const status = trip.status as TripStatus;
  const punchesRaw = (trip.punches as Record<string, unknown>[]) ?? [];
  const enriched = enrichTripLocationsFromPunches(trip, punchesRaw);
  const base = toClientTrip(enriched);
  const punches = punchesRaw.map(toClientPunch);
  const tripLegs = buildLegs(enriched, punches);
  const tripUser = (trip.user as Record<string, unknown> | null) ?? null;
  const legIndex = resolveActiveLegIndex(enriched, tripLegs);
  const activeLeg = tripLegs[legIndex] as Record<string, unknown> | undefined;
  const multiLeg = tripLegs.length > 1;
  const activeDeparted = activeLeg?.departurePunch != null;
  const activeCanArrive =
    activeDeparted && activeLeg?.arrivalPunch == null;

  return {
    ...base,
    requestId: base.tripId,
    userId: trip.userId ?? null,
    user: tripUser,
    requestDate: trip.requestDate ?? trip.createdAt ?? null,
    clientName: enriched.clientName ?? enriched.toLocation ?? null,
    coordinates: {
      originLat: enriched.originLat ?? null,
      originLng: enriched.originLng ?? null,
      destinationLat: enriched.destinationLat ?? null,
      destinationLng: enriched.destinationLng ?? null,
    },
    status: toLegacyTripStatus(status),
    rawStatus: toClientTripStatus(status),
    isActive: isActiveTripStatus(status),
    hasDeparted: multiLeg ? activeDeparted : status !== TripStatus.CREATED,
    canMarkArrival: multiLeg ? activeCanArrive : canMarkArrival(status),
    currentLegIndex: legIndex,
    currentLeg: legIndex + 1,
    tripLegs,
    legs: tripLegs,
    punches,
    totalDistanceKm: trip.totalDistanceKm ?? null,
    travelAllowance: trip.travelAllowance ?? null,
    fuelRatePerKm: trip.fuelRatePerKm ?? null,
  };
}

export function toClientTripDetail(
  trip: Record<string, unknown>,
  routePoints: Record<string, unknown>[] = [],
  trackingCoverageSummary?: Record<string, unknown> | null,
) {
  const summary = toClientTripSummary(trip);
  const route = routePoints.map(toClientRoutePoint);

  return {
    ...summary,
    routePoints: route,
    route,
    trackingCoverageSummary: trackingCoverageSummary ?? null,
  };
}

export function normalizeDeparturePunch(
  body: Record<string, unknown>,
): DeparturePunchDto {
  return {
    timestamp: String(body.timestamp ?? new Date().toISOString()),
    latitude: Number(body.latitude ?? body.lat),
    longitude: Number(body.longitude ?? body.lng ?? body.lon),
    address: body.address ? String(body.address) : undefined,
    batteryPercent:
      body.batteryPercent != null
        ? Number(body.batteryPercent)
        : body.battery_percent != null
          ? Number(body.battery_percent)
          : undefined,
    gpsAccuracy:
      body.gpsAccuracy != null
        ? Number(body.gpsAccuracy)
        : body.gps_accuracy != null
          ? Number(body.gps_accuracy)
          : undefined,
    speed: body.speed != null ? Number(body.speed) : undefined,
    isMockLocation: Boolean(body.isMockLocation ?? body.is_mock_location),
  };
}

export function normalizeArrivalPunch(
  body: Record<string, unknown>,
): ArrivalPunchDto {
  return {
    timestamp: String(body.timestamp ?? new Date().toISOString()),
    latitude: Number(body.latitude ?? body.lat),
    longitude: Number(body.longitude ?? body.lng ?? body.lon),
    address: body.address ? String(body.address) : undefined,
    batteryPercent:
      body.batteryPercent != null
        ? Number(body.batteryPercent)
        : undefined,
    gpsAccuracy:
      body.gpsAccuracy != null ? Number(body.gpsAccuracy) : undefined,
    speed: body.speed != null ? Number(body.speed) : undefined,
  };
}

export function normalizeMeetingStartPunch(
  body: Record<string, unknown>,
): MeetingStartPunchDto {
  return {
    timestamp: String(body.timestamp ?? new Date().toISOString()),
    latitude: Number(body.latitude ?? body.lat),
    longitude: Number(body.longitude ?? body.lng ?? body.lon),
    address: body.address ? String(body.address) : undefined,
  };
}

export function normalizeMeetingEndPunch(
  body: Record<string, unknown>,
): MeetingEndPunchDto {
  return {
    timestamp: String(body.timestamp ?? new Date().toISOString()),
    latitude: Number(body.latitude ?? body.lat),
    longitude: Number(body.longitude ?? body.lng ?? body.lon),
    address: body.address ? String(body.address) : undefined,
    meetingSummary: body.meetingSummary
      ? String(body.meetingSummary)
      : undefined,
    customerNotes: body.customerNotes
      ? String(body.customerNotes)
      : undefined,
  };
}

export function normalizeCreateTripInput(
  body: Record<string, unknown>,
): CreateTripDto {
  const vehicleType = parseVehicleType(
    (body.vehicleType ?? body.vehicle_type) as string | undefined,
  );
  let fuelType = parseFuelType(
    (body.fuelType ?? body.fuel_type) as string | undefined,
  );

  assertFuelTypeForVehicle(vehicleType, fuelType);
  if (vehicleType !== VehicleType.CAR && vehicleType !== VehicleType.BIKE) {
    fuelType = undefined;
  }

  const fromLocation =
    body.fromLocation ??
    body.from ??
    body.fromAddress ??
    body.from_address ??
    body.startLocation ??
    body.start_location;

  const toLocation =
    body.toLocation ??
    body.to ??
    body.toAddress ??
    body.to_address ??
    body.destination ??
    body.destinationName ??
    body.destination_name ??
    body.clientName ??
    body.clientAddress ??
    body.client_address;
  const clientName =
    body.clientName ??
    body.client_name ??
    body.client ??
    body.partyName ??
    body.party_name;

  if (!fromLocation) {
    throw new BadRequestException(
      'fromLocation is required (accepted aliases: from, fromAddress)',
    );
  }
  if (!toLocation) {
    throw new BadRequestException(
      'toLocation is required (accepted aliases: to, destination, clientName, clientAddress)',
    );
  }

  return {
    city: body.city ? String(body.city) : undefined,
    fromLocation: String(fromLocation),
    toLocation: String(toLocation),
    clientName: clientName ? String(clientName) : String(toLocation),
    originLat: body.originLat
      ? Number(body.originLat)
      : body.fromLat
        ? Number(body.fromLat)
        : undefined,
    originLng: body.originLng
      ? Number(body.originLng)
      : body.fromLng
        ? Number(body.fromLng)
        : undefined,
    destinationLat: body.destinationLat
      ? Number(body.destinationLat)
      : undefined,
    destinationLng: body.destinationLng
      ? Number(body.destinationLng)
      : undefined,
    googlePlaceId: body.googlePlaceId
      ? String(body.googlePlaceId)
      : undefined,
    plannedDistance: body.plannedDistance
      ? Number(body.plannedDistance)
      : undefined,
    vehicleType,
    fuelType,
    purpose: body.purpose ? String(body.purpose) : undefined,
  };
}

export function normalizeGpsBatch(body: Record<string, unknown>): GpsBatchDto {
  const rawPoints =
    body.points ?? body.routePoints ?? body.route_points ?? body.batch;
  if (!Array.isArray(rawPoints)) {
    throw new BadRequestException('points array is required');
  }
  return {
    points: rawPoints.map((p: Record<string, unknown>) => ({
      clientPointId: p.clientPointId
        ? String(p.clientPointId)
        : p.client_point_id
          ? String(p.client_point_id)
          : undefined,
      latitude: Number(p.latitude ?? p.lat),
      longitude: Number(p.longitude ?? p.lng ?? p.lon),
      accuracy: p.accuracy != null ? Number(p.accuracy) : undefined,
      speed: p.speed != null ? Number(p.speed) : undefined,
      bearing: p.bearing != null ? Number(p.bearing) : undefined,
      batteryLevel:
        p.batteryLevel != null
          ? Number(p.batteryLevel)
          : p.battery_level != null
            ? Number(p.battery_level)
            : undefined,
      timestamp: String(p.timestamp ?? p.recordedAt ?? p.recorded_at),
      legId: p.legId ? String(p.legId) : p.leg_id ? String(p.leg_id) : undefined,
      sessionId: p.sessionId
        ? String(p.sessionId)
        : p.session_id
          ? String(p.session_id)
          : undefined,
      source: p.source ? String(p.source) : undefined,
    })),
  };
}

export function normalizeTrackingEventsBatch(body: Record<string, unknown>) {
  const raw =
    body.events ?? body.trackingEvents ?? body.tracking_events ?? body.batch;
  if (!Array.isArray(raw)) {
    throw new BadRequestException('events array is required');
  }
  return {
    events: raw.map((e: Record<string, unknown>) => ({
      type: String(e.type),
      timestamp: String(e.timestamp ?? new Date().toISOString()),
      legId: e.legId ? String(e.legId) : e.leg_id ? String(e.leg_id) : undefined,
      sessionId: e.sessionId
        ? String(e.sessionId)
        : e.session_id
          ? String(e.session_id)
          : undefined,
      metadata: e.metadata as Record<string, unknown> | undefined,
    })),
  };
}

/** Fill missing from/to/client from punch GPS addresses when create payload omitted them. */
export function enrichTripLocationsFromPunches(
  trip: Record<string, unknown>,
  punches: Record<string, unknown>[] = [],
): Record<string, unknown> {
  const findPunch = (type: string) =>
    punches.find((p) => String(p.type ?? '').toUpperCase() === type);

  const departure = findPunch('DEPARTURE');
  const arrival = findPunch('ARRIVAL');

  const fromLocation =
    trip.fromLocation ??
    departure?.address ??
    (departure?.latitude != null && departure?.longitude != null
      ? `${departure.latitude}, ${departure.longitude}`
      : null);
  const toLocation =
    trip.toLocation ??
    arrival?.address ??
    (arrival?.latitude != null && arrival?.longitude != null
      ? `${arrival.latitude}, ${arrival.longitude}`
      : null);
  const clientName =
    trip.clientName ?? toLocation ?? arrival?.address ?? null;

  const destinationLat =
    trip.destinationLat ?? arrival?.latitude ?? null;
  const destinationLng =
    trip.destinationLng ?? arrival?.longitude ?? null;

  return {
    ...trip,
    fromLocation,
    toLocation,
    clientName,
    destinationLat,
    destinationLng,
  };
}

export function toClientTrip(trip: Record<string, unknown>) {
  const tripId = String(trip.tripId ?? trip.id ?? '');
  const vehicleType = trip.vehicleType as VehicleType | undefined;
  const fuelType = trip.fuelType as FuelType | undefined;
  const status = trip.status as TripStatus;

  return {
    id: tripId,
    tripId,
    fromLocation: trip.fromLocation ?? null,
    toLocation: trip.toLocation ?? null,
    clientName: trip.clientName ?? null,
    from: trip.fromLocation ?? null,
    to: trip.toLocation ?? null,
    originLat: trip.originLat ?? null,
    originLng: trip.originLng ?? null,
    destinationLat: trip.destinationLat ?? null,
    destinationLng: trip.destinationLng ?? null,
    plannedDistance: trip.plannedDistance ?? null,
    vehicleType: toClientVehicleType(vehicleType),
    fuelType: toClientFuelType(fuelType),
    purpose: trip.purpose ?? null,
    status: status ? toLegacyTripStatus(status) : null,
    rawStatus: status ? toClientTripStatus(status) : null,
    totalDistance: trip.totalDistance ?? 0,
    travelDuration: trip.travelDuration ?? 0,
    startedAt: trip.startedAt ?? null,
    arrivedAt: trip.arrivedAt ?? null,
    completedAt: trip.completedAt ?? null,
    createdAt: trip.createdAt ?? null,
    updatedAt: trip.updatedAt ?? null,
    user: trip.user ?? null,
  };
}
