import { MaterialCommunityIcons } from "@expo/vector-icons";
import type * as ImagePicker from "expo-image-picker";
import { useState } from "react";
import {
  ActivityIndicator,
  Alert,
  Image,
  Text,
  TouchableOpacity,
  View,
} from "react-native";

import { alertPermissionDenied } from "@/lib/capture-permission";
import { driverPhotoUri } from "@/lib/driver-photo";
import { ApiError, fetchAPI } from "@/lib/fetch";
import { loadImagePicker } from "@/lib/image-picker";
import { useT } from "@/lib/i18n";
import { useTheme } from "@/lib/theme";

type PhotoResponse = { data: { photo: string; attached: boolean } };

/**
 * The driver's own photo — the one a rider sees against their name in the list
 * of offers, and checks the arriving driver against.
 *
 * Deliberately not the document scanner: this photo is never read by OCR, it
 * is cropped square because it is rendered in a circle everywhere, and it
 * opens the front camera because it is a picture of a person rather than a
 * piece of paper.
 *
 * Uploading attaches it immediately for a driver who already has a profile, so
 * replacing a bad photo is one tap. During onboarding there is no profile row
 * yet, so the caller keeps the returned name and sends it with the submission.
 */
export const ProfilePhotoPicker = ({
  current,
  onUploaded,
}: {
  /** The photo already on the profile, if any. */
  current?: string | null;
  onUploaded: (photo: string) => void;
}) => {
  const t = useT();
  const { isDark } = useTheme();

  const [preview, setPreview] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);

  // A just-taken photo wins over what the server has, so the driver sees the
  // result of their own tap rather than the picture it replaced.
  const shown = preview ?? driverPhotoUri(current) ?? null;

  const upload = async (asset: ImagePicker.ImagePickerAsset) => {
    if (!asset.base64) {
      Alert.alert(t("driver.photo.errorTitle"), t("driver.photo.errorBody"));
      return;
    }

    setBusy(true);
    try {
      const { data } = (await fetchAPI("/(api)/driver/photo", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ image_base64: asset.base64 }),
      })) as PhotoResponse;

      setPreview(asset.uri);
      onUploaded(data.photo);
    } catch (err) {
      console.log("[DRIVER_PHOTO]: ", err);

      const code =
        err instanceof ApiError
          ? (err.body?.code as string | undefined)
          : undefined;

      Alert.alert(
        t("driver.photo.errorTitle"),
        code === "IMAGE_TOO_LARGE"
          ? t("driver.photo.errorTooLarge")
          : code === "PHOTO_RATE_LIMIT"
            ? t("driver.photo.errorRateLimit")
            : code === "UNSUPPORTED_IMAGE"
              ? t("driver.photo.errorUnsupported")
              : t("driver.photo.errorBody"),
      );
    } finally {
      setBusy(false);
    }
  };

  // Camera only — deliberately no gallery option.
  //
  // This photo is the rider's check that the person who pulled up is the
  // person the app sent them, so it has to be a picture of whoever is holding
  // the phone right now. Letting it come from the gallery would let a driver
  // register with someone else's face, or a photo of a photo, and nothing
  // downstream could tell the difference. It is not proof of identity — a
  // determined faker can point the camera at a printout — but it removes the
  // effortless version of that, and it keeps the photo current.
  const capture = async () => {
    if (busy) return;

    // Loaded on demand — see lib/image-picker. On a binary built before
    // expo-image-picker was added this is the difference between one button
    // not working and the app not starting.
    const picker = loadImagePicker();
    if (!picker) {
      Alert.alert(t("driver.photo.errorTitle"), t("driver.captureUnavailable"));
      return;
    }

    // Everything that touches the picker is wrapped: the availability check
    // above should make a missing native module impossible, but a driver must
    // never be shown a raw "Cannot find native module" either way.
    let result: ImagePicker.ImagePickerResult;

    try {
      const permission = await picker.requestCameraPermissionsAsync();

      if (!permission.granted) {
        alertPermissionDenied(permission, {
          title: t("driver.photo.permissionTitle"),
          message: t("driver.photo.permissionCamera"),
          blocked: t("driver.photo.permissionCameraBlocked"),
          openSettings: t("common.openSettings"),
          cancel: t("common.cancel"),
        });
        return;
      }

      // No crop step: one tap, done. Every surface renders this in a circle
      // with a centre crop anyway, and a selfie is already centred on the face.
      result = await picker.launchCameraAsync({
        mediaTypes: picker.MediaTypeOptions.Images,
        quality: 0.7,
        base64: true,
        exif: false,
        cameraType: picker.CameraType.front,
      });
    } catch (error) {
      console.log("[DRIVER_PHOTO_CAMERA]: ", error);
      Alert.alert(t("driver.photo.errorTitle"), t("driver.captureUnavailable"));
      return;
    }

    if (result.canceled || !result.assets[0]) return;

    await upload(result.assets[0]);
  };

  return (
    <View className="items-center mb-6">
      <TouchableOpacity
        onPress={() => void capture()}
        disabled={busy}
        className="w-28 h-28 rounded-full bg-neutral-100 dark:bg-neutral-900 items-center justify-center overflow-hidden border-2 border-primary-500"
      >
        {busy ? (
          <ActivityIndicator color="#0286ff" />
        ) : shown ? (
          <Image
            source={{ uri: shown }}
            className="w-28 h-28"
            resizeMode="cover"
          />
        ) : (
          <MaterialCommunityIcons
            name="camera-plus-outline"
            size={30}
            color={isDark ? "#9ca3af" : "#858585"}
          />
        )}
      </TouchableOpacity>

      <Text className="text-sm font-JakartaBold text-black dark:text-white mt-3">
        {t("driver.photo.title")}
      </Text>
      <Text className="text-xs font-Jakarta text-general-200 dark:text-neutral-400 text-center mt-1 px-6">
        {t("driver.photo.hint")}
      </Text>

      <TouchableOpacity
        onPress={() => void capture()}
        disabled={busy}
        className="flex-row items-center rounded-full bg-primary-500 py-2.5 px-5 mt-3"
      >
        <MaterialCommunityIcons
          name="camera-outline"
          size={15}
          color="#ffffff"
        />
        <Text className="text-white font-JakartaBold text-xs ml-1.5">
          {shown ? t("driver.photo.retake") : t("driver.photo.take")}
        </Text>
      </TouchableOpacity>
    </View>
  );
};
