import { Request, Response } from "express";
import Trip from "../models/TripModel";
import Vehicle from "../models/vehicleModel";
import { ITrip } from "../interfaces/models/tripInterface";
import ResponseUtil from "../utils/Response/responseUtils";
import { STATUS_CODES } from "../constants/statusCodes";
import { TRIP_CONSTANTS } from "../constants/messages";
import { CustomRequest } from "../interfaces/auth";
import {
  tripSchema,
  tripUpdateSchema,
  tripIdParamSchema,
  tripLifecycleBodySchema,
  tripLocationBodySchema,
} from "../validators/tripValidator";
import { calculateTripMetrics } from "../utils/tripCalculations";
import {
  startTrip,
  completeTrip,
  updateTripLocation,
} from "../services/liveTripService";
import { uploadMulterFileToR2 } from "../services/r2StorageService";

export const addTrip = async (req: CustomRequest, res: Response) => {
  try {
    // Parse stringified JSON fields if they come from multipart/form-data
    let requestBody = { ...req.body };

    // Parse startPoint if it's a string
    if (typeof requestBody.startPoint === "string") {
      try {
        requestBody.startPoint = JSON.parse(requestBody.startPoint);
      } catch (parseError) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.BAD_REQUEST,
          "Invalid startPoint format. Must be valid JSON object.",
        );
      }
    }

    // Parse endPoint if it's a string
    if (typeof requestBody.endPoint === "string") {
      try {
        requestBody.endPoint = JSON.parse(requestBody.endPoint);
      } catch (parseError) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.BAD_REQUEST,
          "Invalid endPoint format. Must be valid JSON object.",
        );
      }
    }

    const tripData = await tripSchema.parseAsync(requestBody);
    const userId = req.userId;

    // Handle uploaded trip image (R2)
    let imageUrl = tripData.imageUrl;

    if (req.file) {
      imageUrl = (await uploadMulterFileToR2(req.file, "trips")).url;
    } else if (req.files && Array.isArray(req.files) && req.files.length > 0) {
      imageUrl = (await uploadMulterFileToR2(req.files[0], "trips")).url;
    }

    // Verify vehicle exists and belongs to user
    const vehicle = await Vehicle.findOne({
      _id: tripData.vehicleId,
      userId: userId,
      isDeleted: false,
    });

    if (!vehicle) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "Vehicle not found or doesn't belong to you.",
      );
    }

    // Calculate trip metrics based on coordinates and vehicle MPG
    const tripMetrics = await calculateTripMetrics(
      tripData.startPoint.lat,
      tripData.startPoint.lng,
      tripData.endPoint.lat,
      tripData.endPoint.lng,
      vehicle.currentMPG,
    );

    const newTrip = new Trip({
      ...tripData,
      userId: userId,
      imageUrl: imageUrl,
      calculatedDistance: tripMetrics.calculatedDistance,
      calculatedMileage: tripMetrics.calculatedMileage,
      calculatedGasRequired: tripMetrics.calculatedGasRequired,
      calculatedMetrics: {
        distance: tripMetrics.formattedDistance,
        mileage: tripMetrics.calculatedMileage,
        gasRequired: tripMetrics.calculatedGasRequired,
      },
    });

    const savedTrip = await newTrip.save();

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        trip: savedTrip,
        calculatedMetrics: {
          distance: tripMetrics.formattedDistance,
          distanceKm: tripMetrics.calculatedDistance,
          mileage: tripMetrics.calculatedMileage,
          gasRequired: tripMetrics.calculatedGasRequired,
          duration: tripMetrics.durationFormatted,
          source: tripMetrics.source,
        },
      },
      TRIP_CONSTANTS.TRIP_ADDED,
    );
  } catch (e: any) {
    // Handle Multer errors specifically
    if (e.name === "MulterError") {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        `File upload error: ${e.message}. Expected field name: 'tripImage'`,
      );
    }
    ResponseUtil.handleError(res, e);
  }
};

export const getAllTrips = async (req: CustomRequest, res: Response) => {
  try {
    const userId = req.userId;
    const statusRaw =
      typeof req.query.status === "string"
        ? req.query.status.trim().toLowerCase()
        : "";

    const filter: Record<string, unknown> = {
      isDeleted: false,
      userId: userId,
    };

    // App tabs: upcoming | completed. Also accept real DB statuses.
    if (statusRaw === "upcoming") {
      // Not finished — planned + currently in progress (exclude completed/cancelled)
      filter.status = { $in: ["planned", "in-progress"] };
    } else if (statusRaw === "completed") {
      filter.status = "completed";
    } else if (
      statusRaw &&
      ["planned", "in-progress", "cancelled"].includes(statusRaw)
    ) {
      filter.status = statusRaw;
    }

    const trips = await Trip.find(filter)
      .populate("vehicleId", "name vehicleModel currentMPG")
      .sort({ createdAt: -1 });

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { trips },
      TRIP_CONSTANTS.TRIPS_FETCHED,
    );
  } catch (error) {
    ResponseUtil.handleError(res, error);
  }
};

export const getTripById = async (req: CustomRequest, res: Response) => {
  try {
    const { id } = req.params;
    const userId = req.userId;

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

    if (!trip) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        TRIP_CONSTANTS.TRIP_NOT_FOUND,
      );
    }

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { trip },
      TRIP_CONSTANTS.TRIP_FETCHED,
    );
  } catch (error) {
    ResponseUtil.handleError(res, error);
  }
};

export const updateTrip = async (req: CustomRequest, res: Response) => {
  try {
    const { id } = req.params;
    const userId = req.userId;

    // Parse stringified JSON fields if they come from multipart/form-data
    let requestBody = { ...req.body };

    // Parse startPoint if it's a string
    if (typeof requestBody.startPoint === "string") {
      try {
        requestBody.startPoint = JSON.parse(requestBody.startPoint);
      } catch (parseError) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.BAD_REQUEST,
          "Invalid startPoint format. Must be valid JSON object.",
        );
      }
    }

    // Parse endPoint if it's a string
    if (typeof requestBody.endPoint === "string") {
      try {
        requestBody.endPoint = JSON.parse(requestBody.endPoint);
      } catch (parseError) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.BAD_REQUEST,
          "Invalid endPoint format. Must be valid JSON object.",
        );
      }
    }

    const updateData = await tripUpdateSchema.parseAsync(requestBody);

    // Handle uploaded trip image (R2)
    if (req.file) {
      (updateData as any).imageUrl = (
        await uploadMulterFileToR2(req.file, "trips")
      ).url;
    }

    // Verify trip exists and belongs to user
    const existingTrip = await Trip.findOne({
      _id: id,
      userId: userId,
      isDeleted: false,
    }).populate("vehicleId", "name vehicleModel currentMPG");

    if (!existingTrip) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        TRIP_CONSTANTS.TRIP_NOT_FOUND,
      );
    }

    // If vehicle is being updated, verify it belongs to user
    if (updateData.vehicleId) {
      const vehicle = await Vehicle.findOne({
        _id: updateData.vehicleId,
        userId: userId,
        isDeleted: false,
      });

      if (!vehicle) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.NOT_FOUND,
          "Vehicle not found or doesn't belong to you.",
        );
      }
    }

    // Recalculate metrics if coordinates are being updated
    if (updateData.startPoint && updateData.endPoint) {
      const vehicle = await Vehicle.findOne({
        _id: updateData.vehicleId || existingTrip.vehicleId,
        userId: userId,
        isDeleted: false,
      });

      if (vehicle) {
        const tripMetrics = await calculateTripMetrics(
          updateData.startPoint.lat,
          updateData.startPoint.lng,
          updateData.endPoint.lat,
          updateData.endPoint.lng,
          vehicle.currentMPG,
        );

        // Add calculated fields to updateData
        (updateData as any).calculatedDistance = tripMetrics.calculatedDistance;
        (updateData as any).calculatedMileage = tripMetrics.calculatedMileage;
        (updateData as any).calculatedGasRequired =
          tripMetrics.calculatedGasRequired;
        (updateData as any).calculatedMetrics = {
          distance: tripMetrics.formattedDistance,
          mileage: tripMetrics.calculatedMileage,
          gasRequired: tripMetrics.calculatedGasRequired,
        };
      }
    }

    const updatedTrip = await Trip.findByIdAndUpdate(id, updateData, {
      new: true,
    }).populate("vehicleId", "name vehicleModel currentMPG");

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { trip: updatedTrip },
      TRIP_CONSTANTS.TRIP_UPDATED,
    );
  } catch (error) {
    ResponseUtil.handleError(res, error);
  }
};

export const deleteTrip = async (req: CustomRequest, res: Response) => {
  try {
    const { id } = req.params;
    const userId = req.userId;

    const deletedTrip = await Trip.findOneAndUpdate(
      { _id: id, userId: userId, isDeleted: false },
      { isDeleted: true },
      { new: true },
    );

    if (!deletedTrip) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        TRIP_CONSTANTS.TRIP_NOT_FOUND,
      );
    }

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { trip: deletedTrip },
      TRIP_CONSTANTS.TRIP_DELETED,
    );
  } catch (error) {
    ResponseUtil.handleError(res, error);
  }
};

export const updateTripStatus = async (req: CustomRequest, res: Response) => {
  try {
    const { id } = req.params;
    const { status } = req.body;
    const userId = req.userId;

    if (
      !["planned", "in-progress", "completed", "cancelled"].includes(status)
    ) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid status. Must be one of: planned, in-progress, completed, cancelled",
      );
    }

    const updatedTrip = await Trip.findOneAndUpdate(
      { _id: id, userId: userId, isDeleted: false },
      { status },
      { new: true },
    ).populate("vehicleId", "name vehicleModel currentMPG");

    if (!updatedTrip) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        TRIP_CONSTANTS.TRIP_NOT_FOUND,
      );
    }

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { trip: updatedTrip },
      "Trip status updated successfully",
    );
  } catch (error) {
    ResponseUtil.handleError(res, error);
  }
};

export const calculateTripMetricsRealtime = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { startPoint, endPoint, vehicleId } = req.body;
    const userId = req.userId;

    // Validate required fields
    if (!startPoint || !endPoint || !vehicleId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "startPoint, endPoint, and vehicleId are required",
      );
    }

    // Validate vehicleId ObjectId format
    if (
      !vehicleId ||
      typeof vehicleId !== "string" ||
      !/^[0-9a-fA-F]{24}$/.test(vehicleId)
    ) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Please enter a valid vehicleId. ObjectId must be a 24-character hexadecimal string.",
      );
    }

    // Validate location points
    if (!startPoint.lat || !startPoint.lng || !startPoint.name) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "startPoint must have lat, lng, and name",
      );
    }

    if (!endPoint.lat || !endPoint.lng || !endPoint.name) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "endPoint must have lat, lng, and name",
      );
    }

    // Verify vehicle exists and belongs to user
    const vehicle = await Vehicle.findOne({
      _id: vehicleId,
      userId: userId,
      isDeleted: false,
    });

    if (!vehicle) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "Vehicle not found or doesn't belong to you.",
      );
    }

    // Calculate trip metrics based on coordinates and vehicle MPG
    const tripMetrics = await calculateTripMetrics(
      startPoint.lat,
      startPoint.lng,
      endPoint.lat,
      endPoint.lng,
      vehicle.currentMPG,
    );

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        startPoint,
        endPoint,
        vehicle: {
          _id: vehicle._id,
          name: vehicle.name,
          vehicleModel: vehicle.vehicleModel,
          currentMPG: vehicle.currentMPG,
        },
        calculatedMetrics: {
          distance: tripMetrics.formattedDistance,
          mileage: tripMetrics.calculatedMileage,
          gasRequired: tripMetrics.calculatedGasRequired,
          duration: tripMetrics.durationFormatted,
          source: tripMetrics.source,
        },
        rawMetrics: {
          distanceInKm: tripMetrics.calculatedDistance,
          distanceInMiles: Math.round(
            tripMetrics.calculatedDistance * 0.621371,
          ),
          durationInSeconds: tripMetrics.duration,
        },
      },
      "Trip metrics calculated successfully",
    );
  } catch (error) {
    console.log("error", error);
    ResponseUtil.handleError(res, error);
  }
};

export const calculateTripMetricsByName = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { startLocationName, endLocationName, vehicleId } = req.body;
    const userId = req.userId;

    // Validate required fields
    if (!startLocationName || !endLocationName || !vehicleId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "startLocationName, endLocationName, and vehicleId are required",
      );
    }

    // Validate vehicleId ObjectId format
    if (
      !vehicleId ||
      typeof vehicleId !== "string" ||
      !/^[0-9a-fA-F]{24}$/.test(vehicleId)
    ) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Please enter a valid vehicleId. ObjectId must be a 24-character hexadecimal string.",
      );
    }

    // Verify vehicle exists and belongs to user
    const vehicle = await Vehicle.findOne({
      _id: vehicleId,
      userId: userId,
      isDeleted: false,
    });

    if (!vehicle) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "Vehicle not found or doesn't belong to you.",
      );
    }

    // For demo purposes, using sample coordinates
    // In a real implementation, you would use a geocoding service like Google Maps API
    const sampleCoordinates: { [key: string]: { lat: number; lng: number } } = {
      "New York": { lat: 40.7128, lng: -74.006 },
      "Los Angeles": { lat: 34.0522, lng: -118.2437 },
      Chicago: { lat: 41.8781, lng: -87.6298 },
      Houston: { lat: 29.7604, lng: -95.3698 },
      Phoenix: { lat: 33.4484, lng: -112.074 },
      Philadelphia: { lat: 39.9526, lng: -75.1652 },
      "San Antonio": { lat: 29.4241, lng: -98.4936 },
      "San Diego": { lat: 32.7157, lng: -117.1611 },
      Dallas: { lat: 32.7767, lng: -96.797 },
      "San Jose": { lat: 37.3382, lng: -121.8863 },
    };

    const startCoords = sampleCoordinates[startLocationName as string];
    const endCoords = sampleCoordinates[endLocationName as string];

    if (!startCoords || !endCoords) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Location not found. Available locations: New York, Los Angeles, Chicago, Houston, Phoenix, Philadelphia, San Antonio, San Diego, Dallas, San Jose",
      );
    }

    // Calculate trip metrics based on coordinates and vehicle MPG
    const tripMetrics = await calculateTripMetrics(
      startCoords.lat,
      startCoords.lng,
      endCoords.lat,
      endCoords.lng,
      vehicle.currentMPG,
    );

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        startPoint: {
          lat: startCoords.lat,
          lng: startCoords.lng,
          name: startLocationName,
        },
        endPoint: {
          lat: endCoords.lat,
          lng: endCoords.lng,
          name: endLocationName,
        },
        vehicle: {
          _id: vehicle._id,
          name: vehicle.name,
          vehicleModel: vehicle.vehicleModel,
          currentMPG: vehicle.currentMPG,
        },
        calculatedMetrics: {
          distance: tripMetrics.formattedDistance,
          mileage: tripMetrics.calculatedMileage,
          gasRequired: tripMetrics.calculatedGasRequired,
          duration: tripMetrics.durationFormatted,
          source: tripMetrics.source,
        },
        rawMetrics: {
          distanceInKm: tripMetrics.calculatedDistance,
          distanceInMiles: Math.round(
            tripMetrics.calculatedDistance * 0.621371,
          ),
          durationInSeconds: tripMetrics.duration,
        },
      },
      "Trip metrics calculated successfully",
    );
  } catch (error) {
    ResponseUtil.handleError(res, error);
  }
};

function handleLiveTripError(res: Response, err: unknown): void {
  if (!(err instanceof Error)) {
    ResponseUtil.errorResponse(
      res,
      STATUS_CODES.INTERNAL_SERVER_ERROR,
      "An unexpected error occurred",
    );
    return;
  }

  switch (err.message) {
    case "NOT_FOUND":
      ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        TRIP_CONSTANTS.TRIP_NOT_FOUND,
      );
      break;
    case "ALREADY_IN_PROGRESS":
      ResponseUtil.errorResponse(
        res,
        STATUS_CODES.CONFLICT,
        TRIP_CONSTANTS.ALREADY_IN_PROGRESS,
      );
      break;
    case "ALREADY_COMPLETED":
      ResponseUtil.errorResponse(
        res,
        STATUS_CODES.CONFLICT,
        TRIP_CONSTANTS.ALREADY_COMPLETED,
      );
      break;
    case "INVALID_START_STATUS":
      ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        TRIP_CONSTANTS.INVALID_START_STATUS,
      );
      break;
    case "INVALID_COMPLETE_STATUS":
      ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        TRIP_CONSTANTS.INVALID_COMPLETE_STATUS,
      );
      break;
    case "NOT_IN_PROGRESS":
      ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        TRIP_CONSTANTS.NOT_IN_PROGRESS,
      );
      break;
    case "LOCATION_RATE_LIMIT":
      ResponseUtil.errorResponse(
        res,
        STATUS_CODES.TOO_MANY_REQUESTS,
        TRIP_CONSTANTS.LOCATION_RATE_LIMIT,
      );
      break;
    default:
      ResponseUtil.errorResponse(
        res,
        STATUS_CODES.INTERNAL_SERVER_ERROR,
        err.message,
      );
  }
}

const LIVE_TRIP_ERRORS = [
  "NOT_FOUND",
  "ALREADY_IN_PROGRESS",
  "ALREADY_COMPLETED",
  "INVALID_START_STATUS",
  "INVALID_COMPLETE_STATUS",
  "NOT_IN_PROGRESS",
  "LOCATION_RATE_LIMIT",
];

/**
 * POST /api/v1/trips/:id/start
 * Start a planned trip (driver live map).
 */
export const startLiveTrip = async (req: CustomRequest, res: Response) => {
  try {
    const { id } = await tripIdParamSchema.parseAsync(req.params);
    const body = await tripLifecycleBodySchema.parseAsync(req.body ?? {});
    const location =
      body.lat !== undefined && body.lng !== undefined
        ? { lat: body.lat, lng: body.lng }
        : undefined;
    const result = await startTrip(req.userId!, id, location);
    ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      result,
      TRIP_CONSTANTS.TRIP_STARTED,
    );
  } catch (err) {
    if (err instanceof Error && LIVE_TRIP_ERRORS.includes(err.message)) {
      handleLiveTripError(res, err);
    } else {
      ResponseUtil.handleError(res, err);
    }
  }
};

/**
 * POST /api/v1/trips/:id/complete
 * Complete an in-progress trip (driver live map).
 */
export const completeLiveTrip = async (req: CustomRequest, res: Response) => {
  try {
    const { id } = await tripIdParamSchema.parseAsync(req.params);
    const body = await tripLifecycleBodySchema.parseAsync(req.body ?? {});
    const location =
      body.lat !== undefined && body.lng !== undefined
        ? { lat: body.lat, lng: body.lng }
        : undefined;
    const result = await completeTrip(req.userId!, id, location);
    ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      result,
      TRIP_CONSTANTS.TRIP_COMPLETED,
    );
  } catch (err) {
    if (err instanceof Error && LIVE_TRIP_ERRORS.includes(err.message)) {
      handleLiveTripError(res, err);
    } else {
      ResponseUtil.handleError(res, err);
    }
  }
};

/**
 * POST /api/v1/trips/:id/location
 * Ping current GPS while trip is in-progress (owner only).
 */
export const pingTripLocation = async (req: CustomRequest, res: Response) => {
  try {
    const { id } = await tripIdParamSchema.parseAsync(req.params);
    const body = await tripLocationBodySchema.parseAsync(req.body ?? {});
    const result = await updateTripLocation(req.userId!, id, body);
    ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      result,
      TRIP_CONSTANTS.LOCATION_UPDATED,
    );
  } catch (err) {
    if (err instanceof Error && LIVE_TRIP_ERRORS.includes(err.message)) {
      handleLiveTripError(res, err);
    } else {
      ResponseUtil.handleError(res, err);
    }
  }
};
