import { Request, Response } from "express";
import axios from "axios";
import Stripe from "stripe";
import { CustomRequest } from "../interfaces/auth";
import ResponseUtil from "../utils/Response/responseUtils";
import { STATUS_CODES } from "../constants/statusCodes";
import { googleMapsConfig } from "../config/googleMaps";
import { stripeConfig } from "../config/stripe";
import { gasStationApprovedForAppFilter } from "../utils/gasStationVisibility";
import { Types, type PipelineStage } from "mongoose";
import GasStationModel from "../models/GasStationModel";
import UserModel from "../models/UserModel";
import { hash, compareSync } from "bcrypt";
import { generateToken } from "../utils/Token";
import { ROLE } from "../constants/enums";
import AuthConfig from "../config/authConfig";
import { randomInt } from "crypto";
import { OtpModel } from "../models/OtpModel";
import { sendEmail } from "../utils/SendEmail";
import { emailTemplateGeneric } from "../utils/SendEmail/templates";
import { AUTH_CONSTANTS } from "../constants/messages";
import { otpExpiresAt } from "../constants/otp";
import {
  registerGasStationSchema,
  registerGasStationWithAccountSchema,
  loginGasStationSchema,
  gasStationAccountSignupSchema,
  gasStationAccountVerifyOtpSchema,
  gasStationResendOtpSchema,
  gasStationForgotPasswordSchema,
  gasStationVerifyResetOtpSchema,
  gasStationResetPasswordSchema,
  updateGasStationSchema,
  connectStripeSchema,
  gasStationsAlongRouteQuerySchema,
} from "../validators/gasStationValidator";
import { ensureStripeCustomerAtRegistration } from "../services/stripeCustomerService";
import { renderStripeConnectLandingPage } from "../utils/stripeConnectLandingPage";
import {
  buildDefaultStripeConnectUrl,
  assertHttpsIfProduction,
} from "../utils/stripeConnectUrls";
import {
  syncGasStationStripeStatusForUser,
  stripeFlagsFromGasStation,
  trySyncGasStationStripeStatus,
} from "../services/gasStationStripeSync";
import { findGasStationsAlongRoute } from "../services/gasStationAlongRouteService";
import { uploadMulterFileToR2 } from "../services/r2StorageService";

/** Normalize userId string for OTP queries (Mongo ObjectId) */
function gasStationOtpUserId(userId: string) {
  if (!Types.ObjectId.isValid(userId)) return null;
  return new Types.ObjectId(userId);
}

/** Map full country name or code to Stripe ISO 3166-1 alpha-2 (e.g. Pakistan -> PK) */
const COUNTRY_TO_STRIPE_CODE: Record<string, string> = {
  pakistan: "PK",
  "united states": "US",
  "united kingdom": "GB",
  india: "IN",
  uae: "AE",
  "united arab emirates": "AE",
  saudi: "SA",
  "saudi arabia": "SA",
  canada: "CA",
};

function getStripeCountryCode(country: string | undefined): string {
  if (!country || country.length === 0) return "US";
  const normalized = country.trim().toLowerCase();
  if (normalized.length === 2) return normalized.toUpperCase();
  return (
    COUNTRY_TO_STRIPE_CODE[normalized] ?? normalized.slice(0, 2).toUpperCase()
  );
}

/** Merge multipart form body with uploaded files so register/update can accept form + files in one request */
async function buildBodyFromForm(
  req: CustomRequest,
): Promise<Record<string, any>> {
  const body =
    typeof req.body === "object" && req.body !== null ? { ...req.body } : {};
  const files = req.files as
    { [key: string]: Express.Multer.File[] } | undefined;
  if (files?.mainImage?.[0]) {
    const { url } = await uploadMulterFileToR2(files.mainImage[0], "stations");
    body.mainImage = url;
  }
  if (files?.logo?.[0]) {
    const { url } = await uploadMulterFileToR2(files.logo[0], "stations");
    body.logoUrl = url;
  }
  if (files?.galleryImages?.length) {
    body.galleryImages = await Promise.all(
      files.galleryImages.map(
        async (f) => (await uploadMulterFileToR2(f, "stations")).url,
      ),
    );
  }
  if (typeof body.operatingHours === "string" && body.operatingHours) {
    try {
      body.operatingHours = JSON.parse(body.operatingHours);
    } catch (_) {}
  }
  if (typeof body.fuelTypes === "string" && body.fuelTypes) {
    if (body.fuelTypes.trim().startsWith("[")) {
      try {
        body.fuelTypes = JSON.parse(body.fuelTypes);
      } catch (_) {}
    } else {
      body.fuelTypes = body.fuelTypes
        .split(",")
        .map((s: string) => s.trim())
        .filter(Boolean);
    }
  }
  if (
    typeof body.galleryImages === "string" &&
    body.galleryImages &&
    !body.galleryImages.startsWith("/")
  ) {
    try {
      body.galleryImages = JSON.parse(body.galleryImages);
    } catch (_) {}
  }
  return body;
}

/** Build gas station API response object (including fuelTypes, hours, media, stripe) */
function toGasStationResponse(station: any, stripeStatus?: string) {
  const s =
    station && typeof station.toObject === "function"
      ? station.toObject()
      : station;
  if (!s) return null;
  const flags = stripeFlagsFromGasStation(s);
  const status = stripeStatus ?? flags.stripeStatus;
  return {
    id: s._id,
    name: s.name,
    address: s.address,
    city: s.city,
    state: s.state,
    zip: s.zip,
    country: s.country,
    phone: s.phone,
    location: {
      lat: s.location?.coordinates?.[1],
      lng: s.location?.coordinates?.[0],
    },
    stripeConnected: flags.stripeConnected,
    isStripeConnected: flags.isStripeConnected,
    stripeChargesEnabled: flags.stripeChargesEnabled,
    stripeDetailsSubmitted: flags.stripeDetailsSubmitted,
    stripeStatus: status,
    // Legacy docs without approvalStatus were live in-app → treat as approved.
    approvalStatus: s.approvalStatus ?? "approved",
    /** FE gate: owner dashboard only when true */
    isAdminApproved: (s.approvalStatus ?? "approved") === "approved",
    approvalNote: s.approvalNote ?? null,
    fuelTypes: s.fuelTypes ?? [],
    operatingHours: s.operatingHours ?? {},
    mainImage: s.mainImage ?? null,
    logoUrl: s.logoUrl ?? null,
    galleryImages: s.galleryImages ?? [],
  };
}

type NearestGasStationSource = "google" | "registered" | "both";

function parseNearestSource(raw: unknown): NearestGasStationSource {
  const s = String(raw ?? "")
    .toLowerCase()
    .trim();
  if (s === "registered") return "registered";
  if (s === "both") return "both";
  return "google";
}

const MAX_GAS_STATIONS_LIST_RADIUS_M = 50000;

type GasStationsListRadiusParsed =
  { ok: true; meters: number } | { ok: false; message: string };

/**
 * GET /gas-stations (geo list): **radius** is in **meters** by default (frontend-friendly).
 * Optional **radiusUnit=km** keeps legacy behavior (whole kilometers).
 * Capped at 50_000 m. Omitted **radius** → 50_000 m.
 */
function parseGasStationsListRadius(
  radiusRaw: unknown,
  radiusUnitRaw: unknown,
): GasStationsListRadiusParsed {
  const missing =
    radiusRaw === undefined ||
    radiusRaw === null ||
    String(radiusRaw).trim() === "";
  if (missing) {
    return { ok: true, meters: MAX_GAS_STATIONS_LIST_RADIUS_M };
  }
  const n = parseInt(String(radiusRaw), 10);
  if (isNaN(n) || n < 0) {
    return { ok: false, message: "Invalid radius value" };
  }
  const u = String(radiusUnitRaw ?? "m")
    .toLowerCase()
    .trim();
  const isKm = u === "km" || u === "kilometer" || u === "kilometers";
  let meters = isKm ? n * 1000 : n;
  if (meters > MAX_GAS_STATIONS_LIST_RADIUS_M) {
    meters = MAX_GAS_STATIONS_LIST_RADIUS_M;
  }
  if (isKm && meters < 1000) {
    return {
      ok: false,
      message: "Radius must be at least 1 km when radiusUnit is km",
    };
  }
  if (!isKm && meters < 1) {
    return { ok: false, message: "Radius must be at least 1 meter" };
  }
  return { ok: true, meters };
}

async function aggregateRegisteredNearestGasStations(
  latitude: number,
  longitude: number,
  maxDistanceMeters: number,
): Promise<Array<Record<string, unknown> & { distMeters: number }>> {
  const pipeline: PipelineStage[] = [
    {
      $geoNear: {
        near: { type: "Point", coordinates: [longitude, latitude] },
        distanceField: "distMeters",
        maxDistance: maxDistanceMeters,
        spherical: true,
        query: gasStationApprovedForAppFilter(),
      },
    },
    { $limit: 50 },
  ];
  return GasStationModel.aggregate(pipeline);
}

function registeredStationToNearestResponse(station: Record<string, unknown>) {
  const distMeters = Number(station.distMeters ?? 0);
  const distKm = distMeters / 1000;
  const stripeConnected = Boolean(station.stripeConnected);
  const stripeChargesEnabled = Boolean(station.stripeChargesEnabled);
  const stripeStatus = stripeConnected
    ? stripeChargesEnabled
      ? "active"
      : "pending"
    : "connect_required";
  const coords = station.location as
    { coordinates?: [number, number] } | undefined;
  return {
    id: String(station._id),
    source: "registered" as const,
    googlePlaceId: (station.googlePlaceId as string | null | undefined) ?? null,
    name: station.name,
    address: station.address,
    city: (station.city as string | undefined) ?? "",
    state: (station.state as string | undefined) ?? "",
    location: {
      lat: coords?.coordinates?.[1],
      lng: coords?.coordinates?.[0],
    },
    distance: {
      meters: Math.round(distMeters),
      kilometers: parseFloat(distKm.toFixed(2)),
      miles: parseFloat((distKm * 0.621371).toFixed(2)),
    },
    stripeStatus,
    fuelTypes: (station.fuelTypes as string[] | undefined) ?? [],
    mainImage: (station.mainImage as string | null | undefined) ?? null,
    logoUrl: (station.logoUrl as string | null | undefined) ?? null,
  };
}

function mapGoogleNearbyResultsToGasStations(
  latitude: number,
  longitude: number,
  results: any[],
) {
  const gasStations = results.map((station: any) => {
    const distance = calculateDistance(
      latitude,
      longitude,
      station.geometry.location.lat,
      station.geometry.location.lng,
    );

    return {
      id: station.place_id,
      source: "google" as const,
      name: station.name,
      address: station.vicinity,
      location: {
        lat: station.geometry.location.lat,
        lng: station.geometry.location.lng,
      },
      distance: {
        meters: Math.round(distance * 1000),
        kilometers: parseFloat(distance.toFixed(2)),
        miles: parseFloat((distance * 0.621371).toFixed(2)),
      },
      rating: station.rating || null,
      totalRatings: station.user_ratings_total || 0,
      isOpen: station.opening_hours?.open_now || null,
      icon: station.icon,
      photos: station.photos
        ? station.photos.map((photo: any) => ({
            reference: photo.photo_reference,
            width: photo.width,
            height: photo.height,
            url: `${googleMapsConfig.baseUrl}/place/photo?maxwidth=${photo.width}&photo_reference=${photo.photo_reference}&key=${googleMapsConfig.apiKey}`,
          }))
        : [],
    };
  });
  gasStations.sort(
    (a: any, b: any) => a.distance.kilometers - b.distance.kilometers,
  );
  return gasStations;
}

/**
 * Registered gas stations along a start→end corridor (live trip map).
 * GET /api/v1/gas-stations/along-route?startLat&startLng&endLat&endLng&bufferKm
 */
export const getGasStationsAlongRoute = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const query = await gasStationsAlongRouteQuerySchema.parseAsync(req.query);
    const stations = await findGasStationsAlongRoute(query);
    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        stations,
        bufferKm: query.bufferKm,
        count: stations.length,
      },
      "Gas stations along route fetched successfully",
    );
  } catch (error) {
    return ResponseUtil.handleError(res, error);
  }
};

/**
 * Get nearest gas stations within a radius.
 * - source=google (default): Google Places Nearby Search only (same as before).
 * - source=registered: Tank Track DB stations only ($geoNear, approved + active).
 * - source=both: registered + Google in separate arrays (Google errors do not fail registered).
 */
export const getNearestGasStations = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { lat, lng, radius, source: sourceRaw } = req.query;

    if (!lat || !lng) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Latitude (lat) and longitude (lng) are required query parameters",
      );
    }

    const latitude = parseFloat(lat as string);
    const longitude = parseFloat(lng as string);

    if (isNaN(latitude) || isNaN(longitude)) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid latitude or longitude values",
      );
    }

    if (latitude < -90 || latitude > 90) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Latitude must be between -90 and 90",
      );
    }

    if (longitude < -180 || longitude > 180) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Longitude must be between -180 and 180",
      );
    }

    let searchRadius = radius ? parseInt(radius as string, 10) * 1000 : 50000;

    if (searchRadius > 50000) {
      searchRadius = 50000;
    }

    if (searchRadius < 1) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Radius must be at least 1 km",
      );
    }

    const source = parseNearestSource(sourceRaw);

    const searchRadiusMeta = {
      kilometers: searchRadius / 1000,
      miles: parseFloat(((searchRadius / 1000) * 0.621371).toFixed(2)),
    };

    const userLocation = { lat: latitude, lng: longitude };

    if (source === "registered") {
      const rows = await aggregateRegisteredNearestGasStations(
        latitude,
        longitude,
        searchRadius,
      );
      const gasStations = rows.map((r) =>
        registeredStationToNearestResponse(r as Record<string, unknown>),
      );
      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          userLocation,
          searchRadius: searchRadiusMeta,
          source: "registered",
          totalResults: gasStations.length,
          gasStations,
        },
        "Registered gas stations fetched successfully",
      );
    }

    if (source === "both") {
      const regRows = await aggregateRegisteredNearestGasStations(
        latitude,
        longitude,
        searchRadius,
      );
      const registeredGasStations = regRows.map((r) =>
        registeredStationToNearestResponse(r as Record<string, unknown>),
      );

      const placesUrl = `${googleMapsConfig.baseUrl}/place/nearbysearch/json`;
      let googleGasStations = [] as ReturnType<
        typeof mapGoogleNearbyResultsToGasStations
      >;
      let googlePlacesStatus: string | null = null;

      try {
        const response = await axios.get(placesUrl, {
          params: {
            location: `${latitude},${longitude}`,
            radius: searchRadius,
            type: "gas_station",
            key: googleMapsConfig.apiKey,
          },
        });
        googlePlacesStatus = response.data.status;
        if (
          response.data.status === "OK" ||
          response.data.status === "ZERO_RESULTS"
        ) {
          googleGasStations = mapGoogleNearbyResultsToGasStations(
            latitude,
            longitude,
            response.data.results ?? [],
          );
        }
      } catch (err: any) {
        console.error(
          "nearest (both): Google Places failed:",
          err?.message ?? err,
        );
        googlePlacesStatus =
          err?.response?.status === 429 ? "OVER_QUERY_LIMIT" : "REQUEST_FAILED";
      }

      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          userLocation,
          searchRadius: searchRadiusMeta,
          source: "both",
          googlePlacesStatus,
          registeredGasStations,
          googleGasStations,
          totalRegistered: registeredGasStations.length,
          totalGoogle: googleGasStations.length,
        },
        "Gas stations fetched successfully",
      );
    }

    const placesUrl = `${googleMapsConfig.baseUrl}/place/nearbysearch/json`;

    const response = await axios.get(placesUrl, {
      params: {
        location: `${latitude},${longitude}`,
        radius: searchRadius,
        type: "gas_station",
        key: googleMapsConfig.apiKey,
      },
    });

    if (
      response.data.status === "OK" ||
      response.data.status === "ZERO_RESULTS"
    ) {
      const gasStations = mapGoogleNearbyResultsToGasStations(
        latitude,
        longitude,
        response.data.results ?? [],
      );

      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          userLocation,
          searchRadius: searchRadiusMeta,
          source: "google",
          totalResults: gasStations.length,
          gasStations,
        },
        "Gas stations fetched successfully",
      );
    } else if (response.data.status === "INVALID_REQUEST") {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid request to Google Places API",
      );
    } else if (response.data.status === "OVER_QUERY_LIMIT") {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.TOO_MANY_REQUESTS,
        "Google Places API quota exceeded. Please try again later.",
      );
    } else {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.INTERNAL_SERVER_ERROR,
        `Google Places API error: ${response.data.status}`,
      );
    }
  } catch (error: any) {
    console.error("Error fetching gas stations:", error.message);

    if (error.response?.status === 429) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.TOO_MANY_REQUESTS,
        "Too many requests to Google Places API. Please try again later.",
      );
    }

    ResponseUtil.handleError(res, error);
  }
};

function mapListedStation(
  station: {
    _id: unknown;
    name: string;
    mainImage?: string | null;
    stripeConnected?: boolean;
    stripeChargesEnabled?: boolean;
    address: string;
    city?: string;
    location?: { coordinates?: [number, number] };
  },
  distMeters?: number,
) {
  const stripeStatus = station.stripeConnected
    ? station.stripeChargesEnabled
      ? "active"
      : "pending"
    : "connect_required";
  const base = {
    id: station._id,
    name: station.name,
    mainImage: station.mainImage ?? null,
    stripeStatus,
    address: station.address,
    city: station.city ?? "",
    location: {
      lat: station.location?.coordinates?.[1],
      lng: station.location?.coordinates?.[0],
    },
  };
  if (distMeters === undefined || distMeters === null) {
    return base;
  }
  const distKm = distMeters / 1000;
  return {
    ...base,
    distance: {
      meters: Math.round(distMeters),
      kilometers: parseFloat(distKm.toFixed(2)),
      miles: parseFloat((distKm * 0.621371).toFixed(2)),
    },
  };
}

/**
 * Get all registered gas stations for app users (paginated).
 * Optional **lat**, **lng**, **radius** (meters by default): when both lat and lng are set, results are filtered and sorted by distance ($geoNear). Use **radiusUnit=km** if **radius** is in kilometers. Response **searchRadius** includes **meters** and computed **kilometers** / **miles**. Omit geo params for legacy sort by createdAt.
 */
export const getAllGasStations = async (req: CustomRequest, res: Response) => {
  try {
    const page = Math.max(parseInt((req.query.page as string) || "1", 10), 1);
    const limit = Math.min(
      Math.max(parseInt((req.query.limit as string) || "20", 10), 1),
      100,
    );
    const skip = (page - 1) * limit;

    const {
      lat: latQ,
      lng: lngQ,
      radius: radiusQ,
      radiusUnit: radiusUnitQ,
    } = req.query;
    const latStr =
      latQ !== undefined && latQ !== null ? String(latQ).trim() : "";
    const lngStr =
      lngQ !== undefined && lngQ !== null ? String(lngQ).trim() : "";
    const hasGeo = latStr.length > 0 && lngStr.length > 0;
    const partialGeo = latStr.length > 0 !== lngStr.length > 0;

    if (partialGeo) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Both lat and lng are required for location-based listing",
      );
    }

    if (hasGeo) {
      const latitude = parseFloat(latStr);
      const longitude = parseFloat(lngStr);

      if (isNaN(latitude) || isNaN(longitude)) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.BAD_REQUEST,
          "Invalid latitude or longitude values",
        );
      }
      if (latitude < -90 || latitude > 90) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.BAD_REQUEST,
          "Latitude must be between -90 and 90",
        );
      }
      if (longitude < -180 || longitude > 180) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.BAD_REQUEST,
          "Longitude must be between -180 and 180",
        );
      }

      const radiusParsed = parseGasStationsListRadius(radiusQ, radiusUnitQ);
      if (!radiusParsed.ok) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.BAD_REQUEST,
          radiusParsed.message,
        );
      }
      const searchRadiusM = radiusParsed.meters;

      const geoQuery = gasStationApprovedForAppFilter();
      const nearStage: PipelineStage = {
        $geoNear: {
          near: { type: "Point", coordinates: [longitude, latitude] },
          distanceField: "distMeters",
          maxDistance: searchRadiusM,
          spherical: true,
          query: geoQuery,
        },
      };

      const [stations, countAgg] = await Promise.all([
        GasStationModel.aggregate([
          nearStage,
          { $skip: skip },
          { $limit: limit },
        ]),
        GasStationModel.aggregate([nearStage, { $count: "c" }]),
      ]);

      const total = countAgg[0]?.c ?? 0;

      const gasStations = stations.map((station: Record<string, unknown>) =>
        mapListedStation(
          station as {
            _id: unknown;
            name: string;
            mainImage?: string | null;
            stripeConnected?: boolean;
            stripeChargesEnabled?: boolean;
            address: string;
            city?: string;
            location?: { coordinates?: [number, number] };
          },
          Number(station.distMeters ?? 0),
        ),
      );

      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          userLocation: { lat: latitude, lng: longitude },
          searchRadius: {
            meters: searchRadiusM,
            kilometers: parseFloat((searchRadiusM / 1000).toFixed(2)),
            miles: parseFloat(((searchRadiusM / 1000) * 0.621371).toFixed(2)),
          },
          total,
          page,
          limit,
          totalPages: Math.ceil(total / limit),
          gasStations,
        },
        "Gas stations fetched successfully",
      );
    }

    const [stations, total] = await Promise.all([
      GasStationModel.find(gasStationApprovedForAppFilter())
        .sort({ createdAt: -1 })
        .skip(skip)
        .limit(limit)
        .lean(),
      GasStationModel.countDocuments(gasStationApprovedForAppFilter()),
    ]);

    const gasStations = stations.map((station) => mapListedStation(station));

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
        gasStations,
      },
      "Gas stations fetched successfully",
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Get registered gas station details by gas station ID (DB record)
 * Returns full details for detail screen.
 */
export const getRegisteredGasStationById = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { gasStationId } = req.params;
    if (!gasStationId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "gasStationId is required",
      );
    }

    const gasStation = await GasStationModel.findOne({
      _id: gasStationId,
      ...gasStationApprovedForAppFilter(),
    }).lean();

    if (!gasStation) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "Gas station not found",
      );
    }

    const stripeStatus = gasStation.stripeConnected
      ? gasStation.stripeChargesEnabled
        ? "active"
        : "pending"
      : "connect_required";

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      gasStation,
      "Gas station details fetched successfully",
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Get details of a specific gas station
 */
export const getGasStationDetails = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { placeId } = req.params;

    if (!placeId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Place ID is required",
      );
    }

    // Call Google Maps Place Details API
    const detailsUrl = `${googleMapsConfig.baseUrl}/place/details/json`;

    const response = await axios.get(detailsUrl, {
      params: {
        place_id: placeId,
        fields:
          "name,formatted_address,formatted_phone_number,geometry,opening_hours,rating,user_ratings_total,website,photos,price_level,reviews",
        key: googleMapsConfig.apiKey,
      },
    });

    if (response.data.status === "OK") {
      const result = response.data.result;

      const gasStationDetails = {
        id: placeId,
        name: result.name,
        address: result.formatted_address,
        phone: result.formatted_phone_number || null,
        website: result.website || null,
        location: {
          lat: result.geometry.location.lat,
          lng: result.geometry.location.lng,
        },
        rating: result.rating || null,
        totalRatings: result.user_ratings_total || 0,
        priceLevel: result.price_level || null,
        openingHours: result.opening_hours
          ? {
              isOpen: result.opening_hours.open_now || false,
              weekdayText: result.opening_hours.weekday_text || [],
            }
          : null,
        photos: result.photos
          ? result.photos.map((photo: any) => ({
              reference: photo.photo_reference,
              width: photo.width,
              height: photo.height,
              url: `${googleMapsConfig.baseUrl}/place/photo?maxwidth=800&photo_reference=${photo.photo_reference}&key=${googleMapsConfig.apiKey}`,
            }))
          : [],
        reviews: result.reviews
          ? result.reviews.map((review: any) => ({
              author: review.author_name,
              rating: review.rating,
              text: review.text,
              time: review.time,
              profilePhoto: review.profile_photo_url,
            }))
          : [],
      };

      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        { gasStation: gasStationDetails },
        "Gas station details fetched successfully",
      );
    } else if (response.data.status === "NOT_FOUND") {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "Gas station not found",
      );
    } else {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.INTERNAL_SERVER_ERROR,
        `Google Places API error: ${response.data.status}`,
      );
    }
  } catch (error: any) {
    console.error("Error fetching gas station details:", error.message);
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Register a gas station (one per user)
 */
export const registerGasStation = async (req: CustomRequest, res: Response) => {
  try {
    const userId = req.userId;
    if (!userId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Unauthorized",
      );
    }

    const body = await registerGasStationSchema.parseAsync(
      await buildBodyFromForm(req),
    );
    const existing = await GasStationModel.findOne({ userId });
    if (existing) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.CONFLICT,
        "You already have a registered gas station. Update it instead.",
      );
    }

    const gasStation = await GasStationModel.create({
      userId,
      name: body.name,
      address: body.address,
      city: body.city ?? "",
      state: body.state ?? "",
      zip: body.zip ?? "",
      country: body.country ?? "",
      phone: body.phone ?? "",
      location: {
        type: "Point",
        coordinates: [body.lng, body.lat],
      },
      googlePlaceId: body.googlePlaceId ?? undefined,
      stripeConnected: false,
      // Owner can log in immediately; dashboard gated until admin approves.
      approvalStatus: "pending",
      approvedAt: undefined,
      fuelTypes: body.fuelTypes ?? [],
      operatingHours: body.operatingHours ?? undefined,
      mainImage: body.mainImage ?? undefined,
      logoUrl: body.logoUrl ?? undefined,
      galleryImages: body.galleryImages ?? [],
    });

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { gasStation: toGasStationResponse(gasStation) },
      "Gas station registered successfully. Waiting for admin approval.",
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Register gas station with email + password (no auth token required).
 * Creates user account if new, or logs in existing user, then creates gas station and returns token.
 */
export const registerGasStationWithAccount = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const body = await registerGasStationWithAccountSchema.parseAsync(
      await buildBodyFromForm(req),
    );
    const {
      email,
      password,
      name,
      address,
      city,
      state,
      zip,
      country,
      phone,
      lat,
      lng,
      googlePlaceId,
      fuelTypes,
      operatingHours,
      mainImage,
      logoUrl,
      galleryImages,
    } = body;

    let user = await UserModel.findOne({ email });
    if (user) {
      if (!user.password) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.BAD_REQUEST,
          "This email is used with social login. Please use the same method or reset password.",
        );
      }
      const passwordMatch = compareSync(password, user.password);
      if (!passwordMatch) {
        return ResponseUtil.errorResponse(
          res,
          STATUS_CODES.BAD_REQUEST,
          "Invalid email or password",
        );
      }
    } else {
      const hashedPassword = await hash(password, 10);
      user = await UserModel.create({
        email,
        password: hashedPassword,
        userType: ROLE.USER,
        isVerified: true,
      });
    }

    await ensureStripeCustomerAtRegistration(String(user._id));
    const stripeRow = await UserModel.findById(user._id)
      .select("stripeCustomerId")
      .lean();
    const stripeCustomerId = stripeRow?.stripeCustomerId ?? undefined;

    const existingStation = await GasStationModel.findOne({ userId: user._id });
    if (existingStation) {
      const token = generateToken({
        email: user.email!,
        id: String(user._id),
        role: user.userType,
      });
      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          token,
          user: {
            id: user._id,
            email: user.email,
            ...(stripeCustomerId ? { stripeCustomerId } : {}),
          },
          gasStation: toGasStationResponse(existingStation),
          message:
            "You already have a gas station. Use the token for further API calls.",
        },
        "Gas station already registered",
      );
    }

    const gasStation = await GasStationModel.create({
      userId: user._id,
      name,
      address,
      city: city ?? "",
      state: state ?? "",
      zip: zip ?? "",
      country: country ?? "",
      phone: phone ?? "",
      location: { type: "Point", coordinates: [lng, lat] },
      googlePlaceId: googlePlaceId ?? undefined,
      stripeConnected: false,
      // Owner can log in immediately; dashboard gated until admin approves.
      approvalStatus: "pending",
      approvedAt: undefined,
      fuelTypes: fuelTypes ?? [],
      operatingHours: operatingHours ?? undefined,
      mainImage: mainImage ?? undefined,
      logoUrl: logoUrl ?? undefined,
      galleryImages: galleryImages ?? [],
    });

    const token = generateToken({
      email: user.email!,
      id: String(user._id),
      role: user.userType,
    });

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        token,
        user: {
          id: user._id,
          email: user.email,
          ...(stripeCustomerId ? { stripeCustomerId } : {}),
        },
        gasStation: toGasStationResponse(gasStation, "connect_required"),
        message:
          "Save the token. Use it in Authorization: Bearer <token> for my-station, connect-stripe, etc. Dashboard unlocks after admin approval.",
      },
      "Gas station registered successfully. Waiting for admin approval.",
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Gas station owner account signup (OTP)
 * Step 1: create account -> OTP sent to email
 */
export const gasStationAccountSignup = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { email, password } = await gasStationAccountSignupSchema.parseAsync(
      req.body,
    );

    const userExist = await UserModel.findOne({ email });
    if (userExist) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Email already exists",
      );
    }

    const hashPassword = await hash(password, String(AuthConfig.SALT));
    const user = await UserModel.create({
      email,
      password: hashPassword,
      userType: ROLE.USER,
      isVerified: false,
    });

    const stripeCustomerId = await ensureStripeCustomerAtRegistration(
      String(user._id),
    );

    const otp = randomInt(100000, 999999);
    const expiry = otpExpiresAt();
    await OtpModel.create({
      userId: user._id,
      otp: String(otp),
      expiry,
    });
    const template = emailTemplateGeneric(otp, "registration");
    await sendEmail(email, AUTH_CONSTANTS.VERIFICATION_CODE, template);

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        userId: user._id,
        email,
        ...(stripeCustomerId ? { stripeCustomerId } : {}),
      },
      AUTH_CONSTANTS.OTP_SENT,
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Gas station owner verify OTP
 * Step 2: verify OTP -> returns token
 */
export const gasStationAccountVerifyOtp = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { otp, userId } = await gasStationAccountVerifyOtpSchema.parseAsync(
      req.body,
    );

    const uid = gasStationOtpUserId(userId);
    if (!uid) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid userId",
      );
    }

    const otpRes = await OtpModel.findOne({ userId: uid });
    if (!otpRes) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "No OTP found or it expired. Use resend OTP or login again to receive a new code.",
      );
    }
    if (new Date() > otpRes.expiry) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        AUTH_CONSTANTS.OTP_EXPIRED,
      );
    }
    if (otpRes.otp !== otp) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        AUTH_CONSTANTS.OTP_MISMATCH,
      );
    }

    const user = await UserModel.findByIdAndUpdate(
      uid,
      { isVerified: true },
      { new: true },
    );
    if (!user || !user.email) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        AUTH_CONSTANTS.USER_NOT_FOUND,
      );
    }

    await OtpModel.deleteMany({ userId: uid });

    const stripeCustomerId = await ensureStripeCustomerAtRegistration(
      String(user._id),
    );

    const token = generateToken({
      email: user.email,
      id: String(user._id),
      role: user.userType,
    });

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        token,
        ...(stripeCustomerId ? { stripeCustomerId } : {}),
      },
      AUTH_CONSTANTS.OTP_VERIFIED,
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Gas station owner resend OTP (after signup, before verify)
 */
export const gasStationResendOtp = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { userId } = await gasStationResendOtpSchema.parseAsync(req.body);

    const uid = gasStationOtpUserId(userId);
    if (!uid) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid userId",
      );
    }

    const user = await UserModel.findById(uid);
    if (!user || !user.email) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        AUTH_CONSTANTS.USER_NOT_FOUND,
      );
    }
    if (user.isVerified) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Account already verified. Use login instead.",
      );
    }

    const otp = randomInt(100000, 999999);
    const expiry = otpExpiresAt();
    await OtpModel.findOneAndUpdate(
      { userId: uid },
      { otp: String(otp), expiry },
      { upsert: true, new: true },
    );

    const template = emailTemplateGeneric(otp, "registration");
    await sendEmail(user.email, AUTH_CONSTANTS.VERIFICATION_CODE, template);

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { userId: user._id, email: user.email },
      AUTH_CONSTANTS.OTP_SENT,
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Gas station owner forgot password
 * Step 1: send OTP to email
 */
export const gasStationForgotPassword = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { email } = await gasStationForgotPasswordSchema.parseAsync(req.body);
    const user = await UserModel.findOne({ email });

    if (!user) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        AUTH_CONSTANTS.USER_NOT_FOUND,
      );
    }

    const otp = randomInt(100000, 999999);
    await OtpModel.findOneAndUpdate(
      { userId: user._id },
      { otp: String(otp), expiry: otpExpiresAt() },
      { upsert: true, new: true },
    );

    const template = emailTemplateGeneric(otp, "forgot_password");
    await sendEmail(email, AUTH_CONSTANTS.VERIFICATION_CODE, template);

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { userId: user._id, email },
      AUTH_CONSTANTS.OTP_SENT,
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Gas station owner verify reset OTP
 * Step 2: validate OTP before showing reset password screen
 */
export const gasStationVerifyResetOtp = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { userId, otp } = await gasStationVerifyResetOtpSchema.parseAsync(
      req.body,
    );
    const uid = gasStationOtpUserId(userId);
    if (!uid) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid userId",
      );
    }
    const otpRes = await OtpModel.findOne({ userId: uid });

    if (!otpRes) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        AUTH_CONSTANTS.OTP_EXPIRED,
      );
    }
    if (otpRes.otp !== otp) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        AUTH_CONSTANTS.OTP_MISMATCH,
      );
    }
    if (new Date() > otpRes.expiry) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        AUTH_CONSTANTS.OTP_EXPIRED,
      );
    }

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { userId: userId, otpVerified: true },
      AUTH_CONSTANTS.OTP_VERIFIED,
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Gas station owner reset password (after verify-reset-otp; OTP already verified on previous screen)
 */
export const gasStationResetPassword = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const { userId, password } = await gasStationResetPasswordSchema.parseAsync(
      req.body,
    );

    const uid = gasStationOtpUserId(userId);
    if (!uid) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid userId",
      );
    }

    const user = await UserModel.findById(uid);
    if (!user) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        AUTH_CONSTANTS.USER_NOT_FOUND,
      );
    }

    user.password = await hash(password, 10);
    user.isVerified = true;
    await user.save();
    await OtpModel.deleteMany({ userId: uid });

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {},
      AUTH_CONSTANTS.PASSWORD_CHANGED,
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Login for gas station owners (email + password). Returns token + user + gasStation (or gasStation: null).
 */
export const loginGasStation = async (req: CustomRequest, res: Response) => {
  try {
    const { email, password } = await loginGasStationSchema.parseAsync(
      req.body,
    );

    const user = await UserModel.findOne({ email });
    if (!user) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.UNAUTHORIZED,
        "Invalid email or password",
      );
    }
    if (!user.password) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "This email is used with social login. Please use the same method to sign in.",
      );
    }
    const passwordMatch = compareSync(password, user.password);
    if (!passwordMatch) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.UNAUTHORIZED,
        "Invalid email or password",
      );
    }

    if (!user.isVerified) {
      const otp = randomInt(100000, 999999);
      const expiry = otpExpiresAt();
      await OtpModel.findOneAndUpdate(
        { userId: user._id },
        { otp: String(otp), expiry },
        { upsert: true, new: true },
      );
      const template = emailTemplateGeneric(otp, "registration");
      await sendEmail(user.email!, AUTH_CONSTANTS.VERIFICATION_CODE, template);
      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          needsVerification: true,
          verified: false,
          userId: user._id,
          email: user.email,
          otpSent: true,
        },
        "Please verify your email. OTP sent to your inbox.",
      );
    }

    const token = generateToken({
      email: user.email!,
      id: String(user._id),
      role: user.userType,
    });

    await trySyncGasStationStripeStatus(String(user._id));

    const gasStation = await GasStationModel.findOne({
      userId: user._id,
    }).lean();
    if (!gasStation) {
      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          token,
          user: { id: user._id, email: user.email },
          gasStation: null,
          stripeStatus: "no_station",
          isStripeConnected: false,
          message:
            "No gas station registered. Use register-with-account or register to add one.",
        },
        "Login successful",
      );
    }

    const gasStationOut = toGasStationResponse(gasStation);

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        token,
        user: { id: user._id, email: user.email },
        gasStation: gasStationOut,
        isStripeConnected: gasStationOut?.isStripeConnected ?? false,
        stripeStatus: gasStationOut?.stripeStatus ?? "connect_required",
        message:
          "Use the token in Authorization: Bearer <token> for my-station, connect-stripe, etc.",
      },
      "Login successful",
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Get current user's registered gas station with Stripe status
 * If stripeConnected is false, frontend should show "Connect Stripe account"
 */
export const getMyGasStation = async (req: CustomRequest, res: Response) => {
  try {
    const userId = req.userId;
    if (!userId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Unauthorized",
      );
    }

    await trySyncGasStationStripeStatus(String(userId));

    const gasStation = await GasStationModel.findOne({ userId }).lean();
    if (!gasStation) {
      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          gasStation: null,
          stripeStatus: "no_station",
          isStripeConnected: false,
          message: "No gas station registered. Register first.",
        },
        "No gas station found",
      );
    }

    const gasStationOut = toGasStationResponse(gasStation);

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {
        gasStation: gasStationOut,
        isStripeConnected: gasStationOut?.isStripeConnected ?? false,
        stripeStatus: gasStationOut?.stripeStatus ?? "connect_required",
      },
      "Gas station fetched successfully",
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Update (edit) current user's gas station. All fields optional – only provided fields are updated.
 * Supports media URLs in JSON body. Use POST /upload-media to upload files and get URLs first.
 */
export const updateGasStation = async (req: CustomRequest, res: Response) => {
  try {
    const userId = req.userId;
    if (!userId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Unauthorized",
      );
    }

    const gasStation = await GasStationModel.findOne({ userId });
    if (!gasStation) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "No gas station registered. Register first.",
      );
    }

    const body = await updateGasStationSchema.parseAsync(
      await buildBodyFromForm(req),
    );
    const update: Record<string, unknown> = {};

    if (body.name !== undefined) update.name = body.name;
    if (body.address !== undefined) update.address = body.address;
    if (body.city !== undefined) update.city = body.city;
    if (body.state !== undefined) update.state = body.state;
    if (body.zip !== undefined) update.zip = body.zip;
    if (body.country !== undefined) update.country = body.country;
    if (body.phone !== undefined) update.phone = body.phone;
    if (body.googlePlaceId !== undefined)
      update.googlePlaceId = body.googlePlaceId ?? null;
    if (body.fuelTypes !== undefined) update.fuelTypes = body.fuelTypes;
    if (body.operatingHours !== undefined)
      update.operatingHours = body.operatingHours;
    if (body.mainImage !== undefined) update.mainImage = body.mainImage ?? null;
    if (body.logoUrl !== undefined) update.logoUrl = body.logoUrl ?? null;
    if (body.galleryImages !== undefined)
      update.galleryImages = body.galleryImages;

    if (body.lat !== undefined && body.lng !== undefined) {
      update.location = { type: "Point", coordinates: [body.lng, body.lat] };
    }

    const updated = await GasStationModel.findByIdAndUpdate(
      gasStation._id,
      { $set: update },
      { new: true },
    ).lean();

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { gasStation: toGasStationResponse(updated) },
      "Gas station updated successfully",
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Upload media for gas station (main image, logo, gallery). Returns URLs to use in register or update.
 * Multipart: mainImage (1 file), logo (1 file), galleryImages (multiple).
 */
export const uploadGasStationMedia = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const userId = req.userId;
    if (!userId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Unauthorized",
      );
    }

    const files = req.files as
      Express.Multer.File[] | { [fieldname: string]: Express.Multer.File[] };
    const result: {
      mainImage?: string;
      logoUrl?: string;
      galleryImages?: string[];
    } = {};

    if (files) {
      if (Array.isArray(files)) {
        if (files.length > 0) {
          result.mainImage = (
            await uploadMulterFileToR2(files[0], "stations")
          ).url;
        }
      } else {
        if (files.mainImage?.[0]) {
          result.mainImage = (
            await uploadMulterFileToR2(files.mainImage[0], "stations")
          ).url;
        }
        if (files.logo?.[0]) {
          result.logoUrl = (
            await uploadMulterFileToR2(files.logo[0], "stations")
          ).url;
        }
        if (files.galleryImages?.length) {
          result.galleryImages = await Promise.all(
            files.galleryImages.map(
              async (f) => (await uploadMulterFileToR2(f, "stations")).url,
            ),
          );
        }
      }
    }

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      result,
      "Upload successful. Use returned URLs in register or update.",
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Create Stripe Connect account link for onboarding
 * Returns URL for frontend to redirect user to connect Stripe
 */
export const connectStripeAccount = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const userId = req.userId;
    if (!userId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Unauthorized",
      );
    }

    if (!stripeConfig.secretKey) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.INTERNAL_SERVER_ERROR,
        "Stripe is not configured",
      );
    }

    const parsed = await connectStripeSchema.parseAsync(req.body ?? {});

    const defaultReturnUrl = buildDefaultStripeConnectUrl(
      "/gas-stations/stripe/return",
    );
    const defaultRefreshUrl = buildDefaultStripeConnectUrl(
      "/gas-stations/stripe/refresh",
    );
    const returnUrl = parsed.returnUrl ?? defaultReturnUrl;
    const refreshUrl = parsed.refreshUrl ?? defaultRefreshUrl;

    if (!returnUrl || !refreshUrl) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Stripe return URL is not configured. Set PUBLIC_API_URL on the server, or pass returnUrl/refreshUrl in the request body (must be a public, no-JWT page).",
      );
    }

    try {
      assertHttpsIfProduction(returnUrl, "returnUrl");
      assertHttpsIfProduction(refreshUrl, "refreshUrl");
    } catch (e) {
      const message = e instanceof Error ? e.message : "Invalid callback URL";
      return ResponseUtil.errorResponse(res, STATUS_CODES.BAD_REQUEST, message);
    }

    const gasStation = await GasStationModel.findOne({ userId });
    if (!gasStation) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "Register a gas station first",
      );
    }

    const appendUid = (rawUrl: string): string => {
      const url = new URL(rawUrl);
      url.searchParams.set("uid", String(userId));
      return url.toString();
    };
    const finalReturnUrl = appendUid(returnUrl);
    const finalRefreshUrl = appendUid(refreshUrl);
    const stripe = new Stripe(stripeConfig.secretKey, {
      apiVersion: "2026-01-28.clover",
    });

    const createAccountAndLink = async (): Promise<{
      url: string;
      accountId: string;
    }> => {
      const account = await stripe.accounts.create({
        type: "express",
        country: "US",
        capabilities: {
          card_payments: { requested: true },
          transfers: { requested: true },
        },
      });
      const newAccountId = account.id;
      gasStation.stripeAccountId = newAccountId;
      await gasStation.save();

      const accountLink = await stripe.accountLinks.create({
        account: newAccountId,
        refresh_url: finalRefreshUrl,
        return_url: finalReturnUrl,
        type: "account_onboarding",
      });
      return { url: accountLink.url, accountId: newAccountId };
    };

    let accountId = gasStation.stripeAccountId;

    if (!accountId) {
      const result = await createAccountAndLink();
      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          url: result.url,
          stripeAccountId: result.accountId,
          message: "Redirect user to this URL to connect Stripe",
        },
        "Stripe Connect link created",
      );
    }

    try {
      const accountLink = await stripe.accountLinks.create({
        account: accountId,
        refresh_url: finalRefreshUrl,
        return_url: finalReturnUrl,
        type: "account_onboarding",
      });
      return ResponseUtil.successResponse(
        res,
        STATUS_CODES.SUCCESS,
        {
          url: accountLink.url,
          stripeAccountId: accountId,
          message: "Redirect user to this URL to connect Stripe",
        },
        "Stripe Connect link created",
      );
    } catch (linkError: any) {
      const isInvalidAccount =
        linkError?.message?.includes("not connected to your platform") ||
        linkError?.message?.includes("does not exist") ||
        linkError?.code === "resource_missing";

      if (isInvalidAccount) {
        gasStation.stripeAccountId = undefined;
        await gasStation.save();
        const result = await createAccountAndLink();
        return ResponseUtil.successResponse(
          res,
          STATUS_CODES.SUCCESS,
          {
            url: result.url,
            stripeAccountId: result.accountId,
            message: "Redirect user to this URL to connect Stripe",
          },
          "Stripe Connect link created",
        );
      }
      throw linkError;
    }
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/** Public callback page for Stripe return_url */
export const stripeConnectReturnPage = async (req: Request, res: Response) => {
  const renderReturnPage = (
    opts: Omit<Parameters<typeof renderStripeConnectLandingPage>[0], "kind">,
  ) => renderStripeConnectLandingPage({ ...opts, kind: "return" });

  try {
    const uid = typeof req.query.uid === "string" ? req.query.uid : "";
    if (!uid) {
      return res
        .status(400)
        .type("html")
        .send(
          renderReturnPage({
            title: "Invalid request",
            subtitle: "Missing user identifier in callback URL.",
            ok: false,
          }),
        );
    }
    const status = await syncGasStationStripeStatusForUser(uid);
    return res
      .status(200)
      .type("html")
      .send(
        renderReturnPage({
          title: "Tank Track Stripe Connected",
          subtitle: "Your gas station payout account is linked successfully.",
          statusText: `status: ${status.stripeStatus}`,
          ok: true,
        }),
      );
  } catch (error: any) {
    return res
      .status(500)
      .type("html")
      .send(
        renderReturnPage({
          title: "Connection Failed",
          subtitle: error?.message || "Unable to complete Stripe callback.",
          ok: false,
        }),
      );
  }
};

/** Public callback page for Stripe refresh_url */
export const stripeConnectRefreshPage = async (req: Request, res: Response) => {
  const renderRefreshPage = (
    opts: Omit<Parameters<typeof renderStripeConnectLandingPage>[0], "kind">,
  ) => renderStripeConnectLandingPage({ ...opts, kind: "refresh" });

  try {
    const uid = typeof req.query.uid === "string" ? req.query.uid : "";
    if (!uid) {
      return res
        .status(400)
        .type("html")
        .send(
          renderRefreshPage({
            title: "Invalid request",
            subtitle: "Missing user identifier in refresh URL.",
            ok: false,
          }),
        );
    }
    await syncGasStationStripeStatusForUser(uid);
    return res
      .status(200)
      .type("html")
      .send(
        renderRefreshPage({
          title: "Session Refreshed",
          subtitle: "Please go back and continue Stripe onboarding.",
          ok: true,
        }),
      );
  } catch (error: any) {
    return res
      .status(500)
      .type("html")
      .send(
        renderRefreshPage({
          title: "Refresh Failed",
          subtitle: error?.message || "Could not refresh Stripe session.",
          ok: false,
        }),
      );
  }
};

/**
 * Refresh Stripe status from Stripe API (e.g. after user returns from onboarding)
 */
export const refreshStripeStatus = async (
  req: CustomRequest,
  res: Response,
) => {
  try {
    const userId = req.userId;
    if (!userId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Unauthorized",
      );
    }

    if (!stripeConfig.secretKey) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.INTERNAL_SERVER_ERROR,
        "Stripe is not configured",
      );
    }

    const data = await syncGasStationStripeStatusForUser(String(userId));
    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      data,
      "Stripe status refreshed",
    );
  } catch (error: any) {
    ResponseUtil.handleError(res, error);
  }
};

/**
 * Calculate distance between two points using Haversine formula
 */
function calculateDistance(
  lat1: number,
  lng1: number,
  lat2: number,
  lng2: number,
): number {
  const R = 6371; // Earth's radius in kilometers
  const dLat = (lat2 - lat1) * (Math.PI / 180);
  const dLng = (lng2 - lng1) * (Math.PI / 180);
  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(lat1 * (Math.PI / 180)) *
      Math.cos(lat2 * (Math.PI / 180)) *
      Math.sin(dLng / 2) *
      Math.sin(dLng / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c; // Distance in kilometers
}
