import { MaterialCommunityIcons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { useCallback, useEffect, useRef, useState } from "react";
import {
  ActivityIndicator,
  Alert,
  Image,
  ScrollView,
  Text,
  TouchableOpacity,
  View,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";

import { CancelSheet } from "@/components/cancel-sheet";
import { CustomButton } from "@/components/custom-button";
import { Map } from "@/components/map";
import { OfferList } from "@/components/offer-list";
import { PaymentChoiceSheet } from "@/components/payment-choice-sheet";
import { RatingSheet } from "@/components/rating-sheet";
import { icons, images } from "@/constants";
import { driverPhotoUri } from "@/lib/driver-photo";
import { ApiError, fetchAPI } from "@/lib/fetch";
import { useT } from "@/lib/i18n";
import { payByCard, selectDriver } from "@/lib/request-ride";
import { useSession } from "@/lib/session";
import { formatTime } from "@/lib/utils";
import { useLocationStore } from "@/store";
import type { Ride, RideOffer } from "@/types/type";

const POLL_MS = 3000;

// While the request is open, offers arrive one driver at a time and the rider
// is staring at the list waiting for them. A three-second gap between a driver
// tapping Offer and their face appearing reads as nothing happening.
const OPEN_POLL_MS = 1500;

const STATUS_KEY: Record<string, string> = {
  requested: "bookRide.status.requested",
  accepted: "bookRide.status.accepted",
  arrived: "bookRide.status.arrived",
  en_route: "bookRide.status.enRoute",
  completed: "bookRide.status.completed",
  cancelled: "bookRide.status.cancelled",
  expired: "bookRide.status.expired",
};

const TERMINAL = ["completed", "cancelled", "expired"];

// book-ride is now the live ride-status screen. The rider lands here after
// requesting a ride and polls its status until it completes (or they cancel).
const BookRide = () => {
  const { id } = useLocalSearchParams<{ id: string }>();
  const rideId = Number(id);
  const t = useT();
  const { user } = useSession();
  const setUserLocation = useLocationStore((s) => s.setUserLocation);
  const setDestinationLocation = useLocationStore(
    (s) => s.setDestinationLocation,
  );
  const clearDestination = useLocationStore((s) => s.clearDestination);

  const [ride, setRide] = useState<Ride | null>(null);
  const [loading, setLoading] = useState(true);
  const [cancelling, setCancelling] = useState(false);
  // The offer the rider tapped, held while they choose how to pay.
  const [picked, setPicked] = useState<RideOffer | null>(null);
  const [paying, setPaying] = useState(false);
  // A card order that was paid but whose selection then failed. Kept so the
  // rider can pick a different driver without paying a second time — the
  // server only consumes an order when a driver is actually assigned.
  const paidOrder = useRef<string | null>(null);
  // Server clock minus device clock, so the elapsed counter is measured on the
  // clock the request window is actually enforced against.
  const clockOffset = useRef(0);
  const [error, setError] = useState<string | null>(null);
  const [cancelOpen, setCancelOpen] = useState(false);
  // Set once, when the ride first lands on 'completed' during this session,
  // so dismissing the sheet doesn't immediately re-open it on the next poll.
  const [ratingOpen, setRatingOpen] = useState(false);
  const [ratingHandled, setRatingHandled] = useState(false);

  const load = useCallback(async () => {
    try {
      const res = await fetchAPI(`/(api)/ride/${rideId}`);
      const r = res.data as Ride;
      if (r.now) clockOffset.current = Date.parse(r.now) - Date.now();
      setRide(r);

      // Ask for the rating the moment the driver ends the trip — the rider is
      // still in the car and still remembers. `my_rating` covers the case
      // where they already rated from the home banner.
      if (r.status === "completed" && r.my_rating == null && !ratingHandled) {
        setRatingOpen(true);
      }

      // Keep the map's origin/destination in sync with the ride so the route
      // line renders even if the rider reached this screen via history.
      setUserLocation({
        latitude: r.origin_latitude,
        longitude: r.origin_longitude,
        address: r.origin_address,
      });
      setDestinationLocation({
        latitude: r.destination_latitude,
        longitude: r.destination_longitude,
        address: r.destination_address,
      });
    } catch (err) {
      console.log("[BOOK_RIDE_LOAD]: ", err);
      if (err instanceof ApiError && err.status === 404) {
        setError(t("bookRide.rideNotFound"));
      }
    } finally {
      setLoading(false);
    }
  }, [rideId, setUserLocation, setDestinationLocation, ratingHandled, t]);

  useEffect(() => {
    void load();
  }, [load]);

  // Drop the route when the rider leaves this screen.
  //
  // Nothing used to clear it, so a destination survived for the life of the
  // process — and since backgrounding an app doesn't end that process, the
  // next launch drew a line to a trip that had already finished. Cleared on
  // unmount rather than on completion because `load` re-sets it on every poll:
  // clearing while still on screen would just fight the next poll, and the
  // tracking map would lose the route the rider is watching.
  useEffect(() => () => clearDestination(), [clearDestination]);

  // Poll while the ride is still in a non-terminal state, quickly while
  // offers are still coming in.
  useEffect(() => {
    const status = ride?.status;
    if (!status || TERMINAL.includes(status)) return;
    const every = status === "requested" ? OPEN_POLL_MS : POLL_MS;
    const timer = setInterval(() => void load(), every);
    return () => clearInterval(timer);
  }, [ride?.status, load]);

  // Take one of the offers. This is the call that assigns the ride: it pays
  // (or commits to cash), locks in that driver and releases the others.
  //
  // A 409 means the driver was taken while the rider was deciding — a normal
  // outcome of several riders competing for the same cars, not an error. The
  // list simply reloads without them, and any card payment already made stays
  // unspent and is reused for the next pick.
  const pay = async (method: "cash" | "card") => {
    const offer = picked;
    if (!offer || !ride) return;

    setPaying(true);
    try {
      let orderId = paidOrder.current ?? undefined;

      if (method === "card" && !orderId) {
        orderId = await payByCard({
          ride,
          user: { name: user?.name ?? "", email: user?.email ?? "" },
        });
        paidOrder.current = orderId;
      }

      await selectDriver({
        rideId,
        offerId: offer.offer_id,
        method,
        orderId: method === "card" ? orderId : undefined,
      });

      // Assigned: the money is spent and the ride has a driver.
      paidOrder.current = null;
      setPicked(null);
      await load();
    } catch (err) {
      console.log("[BOOK_RIDE_SELECT]: ", err);
      setPicked(null);

      if (err instanceof ApiError && err.status === 409) {
        Alert.alert(
          t("bookRide.offers.goneTitle"),
          paidOrder.current
            ? t("bookRide.offers.goneBodyPaid")
            : t("bookRide.offers.goneBody"),
        );
      } else {
        Alert.alert(
          t("bookRide.alertErrorTitle"),
          err instanceof ApiError ? err.message : t("bookRide.match.alertBody"),
        );
      }
      await load();
    } finally {
      setPaying(false);
    }
  };

  const cancel = async (reason: string) => {
    setCancelling(true);
    try {
      await fetchAPI(`/(api)/ride/${rideId}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ status: "cancelled", reason }),
      });
      setCancelOpen(false);
      await load();
    } catch (err) {
      console.log("[BOOK_RIDE_CANCEL]: ", err);
      Alert.alert(t("bookRide.alertErrorTitle"), t("bookRide.alertErrorBody"));
    } finally {
      setCancelling(false);
    }
  };

  if (loading) {
    return (
      <SafeAreaView className="flex-1 bg-white dark:bg-neutral-950 items-center justify-center">
        <ActivityIndicator size="large" color="#0286ff" />
      </SafeAreaView>
    );
  }

  if (error || !ride) {
    return (
      <SafeAreaView className="flex-1 bg-white dark:bg-neutral-950 items-center justify-center px-7">
        <Text className="text-base text-general-200 dark:text-neutral-400 text-center">
          {error ?? t("bookRide.couldNotLoad")}
        </Text>
        <CustomButton
          title={t("bookRide.backHome")}
          onPress={() => router.replace("/(root)/(tabs)/home")}
          className="mt-6"
        />
      </SafeAreaView>
    );
  }

  const driver = ride.driver;
  const driverId = driver.id;
  const terminal = TERMINAL.includes(ride.status);
  const driverName = [driver.first_name, driver.last_name]
    .filter(Boolean)
    .join(" ");
  const cashDue = ride.payment_status === "cash";
  const offers = (ride.offers ?? []) as RideOffer[];
  // Whole seconds the search has been running, measured on the server's clock.
  const searchSeconds = Math.max(
    0,
    Math.round(
      (Date.now() + clockOffset.current - Date.parse(ride.created_at)) / 1000,
    ),
  );

  return (
    <SafeAreaView className="flex-1 bg-general-500 dark:bg-neutral-950">
      <View className="h-[45%] bg-blue-500">
        <Map trackedDriver={driverId ? { ...driver, id: driverId } : null} />
      </View>

      {/* Scrollable, because the number of things below the map isn't fixed:
          four drivers offering on a request push the fare, the cancel button
          — and the fourth driver — off the bottom of the screen, and a rider
          who can't reach an offer can't take it. */}
      <ScrollView
        className="flex-1 px-5 pt-4"
        contentContainerStyle={{ flexGrow: 1, paddingBottom: 24 }}
      >
        <Text className="text-2xl font-JakartaExtraBold mb-2 text-black dark:text-white">
          {/* Once drivers have volunteered the screen stops being a search and
              becomes a decision, and the heading has to say which one it is —
              a rider reading "finding your driver" over a list of drivers
              doesn't know it's waiting on them. */}
          {ride.status === "requested" && offers.length > 0
            ? t("bookRide.status.choosing")
            : STATUS_KEY[ride.status]
              ? t(STATUS_KEY[ride.status])
              : ride.status}
        </Text>

        {/* Waiting on the first driver to volunteer. The elapsed counter is
            there because a spinner with no number on it reads as broken after
            about ten seconds — and the request legitimately sits open for a
            couple of minutes. A rider who can see it counting knows their
            request is still live. */}
        {ride.status === "requested" && offers.length === 0 ? (
          <View className="bg-white dark:bg-neutral-900 rounded-2xl p-5 mt-2 items-center">
            <ActivityIndicator size="large" color="#0286ff" />
            <Text className="text-general-200 dark:text-neutral-400 mt-3 text-center">
              {t("bookRide.matchingDriver", { service: ride.service })}
            </Text>
            <Text className="text-xs text-general-200 dark:text-neutral-400 mt-2">
              {t("bookRide.searchingFor", { seconds: searchSeconds })}
            </Text>
          </View>
        ) : null}

        {/* Drivers who want the job. The rider picks; everyone else is let go
            the moment they do. */}
        {ride.status === "requested" && offers.length > 0 ? (
          <OfferList
            offers={offers}
            pendingOfferId={paying ? (picked?.offer_id ?? null) : null}
            busy={paying}
            onPick={setPicked}
          />
        ) : null}

        {/* Pickup code — the rider's half of the handshake. Shown from the
            moment a driver is assigned until the trip starts; the driver
            can't start without hearing it, which is what stops a rider from
            getting into the wrong car (and the wrong car from taking them). */}
        {ride.pickup_code ? (
          <View
            className={`rounded-2xl p-4 mt-2 items-center ${
              ride.status === "arrived"
                ? "bg-emerald-500"
                : "bg-white dark:bg-neutral-900"
            }`}
          >
            <Text
              className={`text-xs font-JakartaMedium ${
                ride.status === "arrived"
                  ? "text-white/90"
                  : "text-general-200 dark:text-neutral-400"
              }`}
            >
              {ride.status === "arrived"
                ? t("bookRide.driverHere")
                : t("bookRide.pickupCodeLabel")}
            </Text>
            <Text
              className={`text-4xl font-JakartaExtraBold tracking-[8px] mt-1 ${
                ride.status === "arrived"
                  ? "text-white"
                  : "text-black dark:text-white"
              }`}
            >
              {ride.pickup_code}
            </Text>
            <Text
              className={`text-xs text-center mt-1 ${
                ride.status === "arrived"
                  ? "text-white/90"
                  : "text-general-200 dark:text-neutral-400"
              }`}
            >
              {t("bookRide.pickupCodeHint")}
            </Text>
          </View>
        ) : null}

        {/* Driver card — shown once the pairing is confirmed. While the ride
            is still 'matched' the confirmation card above is showing the same
            driver, and two cards for one driver reads as two drivers. */}
        {driver?.id && ride.status !== "matched" ? (
          <View className="bg-white dark:bg-neutral-900 rounded-2xl p-4 mt-2">
            <View className="flex-row items-center">
              <Image
                source={{ uri: driverPhotoUri(driver.profile_image_url) }}
                className="w-16 h-16 rounded-full"
              />
              <View className="ml-4 flex-1">
                <Text className="text-lg font-JakartaSemiBold text-black dark:text-white">
                  {driver.first_name} {driver.last_name}
                </Text>
                <View className="flex-row items-center mt-1">
                  <Image source={icons.star} className="w-4 h-4" />
                  <Text className="ml-1 text-general-200 dark:text-neutral-400">
                    {driver.rating?.toFixed(1) ?? t("bookRide.ratingFallback")}
                  </Text>
                  {driver.car_model ? (
                    <Text className="ml-3 text-general-200 dark:text-neutral-400">
                      {driver.car_model}
                    </Text>
                  ) : null}
                </View>
              </View>
              <View className="flex-row items-center">
                <Text className="text-xs text-general-200 dark:text-neutral-400 capitalize mr-3">
                  {driver.service ?? ride.service}
                </Text>
                {/* Call the driver — only while the ride is active. */}
                {!terminal ? (
                  <TouchableOpacity
                    onPress={() =>
                      router.push({
                        pathname: "/(root)/call",
                        params: { rideId: String(ride.ride_id), mode: "start" },
                      })
                    }
                    hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
                    accessibilityLabel={t("chat.call")}
                    className="w-9 h-9 rounded-full bg-general-400 items-center justify-center"
                  >
                    <MaterialCommunityIcons
                      name="phone"
                      size={18}
                      color="white"
                    />
                  </TouchableOpacity>
                ) : null}
              </View>
            </View>

            <View className="flex-row items-center gap-x-2 mt-4">
              <Image source={icons.to} className="w-4 h-4" />
              <Text
                className="font-JakartaMedium text-sm text-black dark:text-white"
                numberOfLines={1}
              >
                {ride.origin_address}
              </Text>
            </View>
            <View className="flex-row items-center gap-x-2 mt-2">
              <Image source={icons.point} className="w-4 h-4" />
              <Text
                className="font-JakartaMedium text-sm text-black dark:text-white"
                numberOfLines={1}
              >
                {ride.destination_address}
              </Text>
            </View>

            <View className="flex-row justify-between mt-4 pt-3 border-t border-neutral-100 dark:border-neutral-800">
              <Text className="text-general-200 dark:text-neutral-400 text-xs">
                {ride.payment_status === "cash"
                  ? t("bookRide.paymentCash")
                  : t("bookRide.paymentCard")}
              </Text>
              <Text className="font-JakartaBold text-emerald-600 dark:text-emerald-400">
                ${(ride.fare_price / 100).toFixed(2)}
              </Text>
            </View>
          </View>
        ) : null}

        {/* Completed summary */}
        {ride.status === "completed" ? (
          <View className="bg-white dark:bg-neutral-900 rounded-2xl p-4 mt-4 items-center">
            <Image source={images.check} className="w-12 h-12" />
            <Text className="text-lg font-JakartaBold mt-3 text-black dark:text-white">
              {t("bookRide.fare", { fare: (ride.fare_price / 100).toFixed(2) })}
            </Text>
            <Text className="text-general-200 dark:text-neutral-400 text-sm mt-1">
              {t("bookRide.tripTime", { time: formatTime(ride.ride_time) })}
            </Text>
            {/* A cash ride the driver hasn't marked collected is money still
                owed — say so rather than showing a clean "all done". */}
            {cashDue ? (
              <Text className="text-amber-600 dark:text-amber-400 text-sm mt-2 text-center">
                {t("bookRide.cashDue", {
                  amount: (ride.fare_price / 100).toFixed(2),
                })}
              </Text>
            ) : null}
            {ride.my_rating ? (
              <Text className="text-general-200 dark:text-neutral-400 text-sm mt-2">
                {t("bookRide.youRated", { n: ride.my_rating })}
              </Text>
            ) : (
              <TouchableOpacity
                onPress={() => setRatingOpen(true)}
                className="mt-3"
              >
                <Text className="font-JakartaBold text-primary-500">
                  {t("bookRide.rateDriver")}
                </Text>
              </TouchableOpacity>
            )}
          </View>
        ) : null}

        {/* Cancelled / expired */}
        {ride.status === "cancelled" || ride.status === "expired" ? (
          <View className="bg-white dark:bg-neutral-900 rounded-2xl p-4 mt-4 items-center">
            <Text className="text-general-200 dark:text-neutral-400 text-center">
              {ride.status === "expired"
                ? t("bookRide.noDriversFound")
                : ride.cancelled_by === "driver"
                  ? t("bookRide.cancelledByDriver")
                  : t("bookRide.rideCancelled")}
            </Text>
          </View>
        ) : null}

        <View className="mt-auto pt-6">
          {terminal ? (
            <CustomButton
              title={t("bookRide.backHome")}
              onPress={() => router.replace("/(root)/(tabs)/home")}
            />
          ) : ride.status === "en_route" ? (
            // Once the trip is under way there is nothing to cancel — the
            // rider is in the car. Ending it early is the driver's action.
            <Text className="text-center text-general-200 dark:text-neutral-400 text-sm pb-3">
              {t("bookRide.enRouteNotice")}
            </Text>
          ) : (
            <TouchableOpacity
              onPress={() => setCancelOpen(true)}
              disabled={cancelling}
              className="rounded-full py-3 bg-white dark:bg-neutral-900 items-center border border-rose-300 dark:border-rose-900"
            >
              <Text className="font-JakartaBold text-rose-500">
                {cancelling
                  ? t("bookRide.cancelling")
                  : t("bookRide.cancelRide")}
              </Text>
            </TouchableOpacity>
          )}
        </View>
      </ScrollView>

      <PaymentChoiceSheet
        visible={picked !== null}
        driverName={
          picked
            ? [picked.first_name, picked.last_name].filter(Boolean).join(" ")
            : null
        }
        fareCents={ride.fare_price}
        submitting={paying}
        onPay={(method) => void pay(method)}
        onCancel={() => setPicked(null)}
      />

      <CancelSheet
        visible={cancelOpen}
        audience="rider"
        submitting={cancelling}
        onCancel={() => setCancelOpen(false)}
        onConfirm={(reason) => void cancel(reason)}
      />

      <RatingSheet
        visible={ratingOpen}
        rideId={rideId}
        audience="rider"
        subjectName={driverName || null}
        subjectAvatar={driver.profile_image_url}
        onDone={() => {
          setRatingOpen(false);
          setRatingHandled(true);
          void load();
        }}
        onSkip={() => {
          setRatingOpen(false);
          setRatingHandled(true);
        }}
      />
    </SafeAreaView>
  );
};

export default BookRide;
