import { Types } from "mongoose";
import TripShareModel from "../models/TripShareModel";
import Trip from "../models/TripModel";
import UserModel from "../models/UserModel";
import { isConnected, PaginatedResult } from "./connectionService";
import { assertNotBlocked } from "./blockService";
import { buildImageUrl } from "../utils/imageUrl";
import { notify } from "./notificationService";

function mapUserSummary(user: {
  _id: unknown;
  fullName?: string;
  email?: string;
  image?: string | null;
} | null) {
  if (!user) return null;
  return {
    _id: user._id,
    fullName: user.fullName ?? "",
    email: user.email ?? "",
    image: user.image ? buildImageUrl(user.image) : null,
  };
}

function mapVehicleSummary(vehicle: unknown) {
  if (!vehicle || typeof vehicle !== "object") return null;
  const v = vehicle as {
    _id?: unknown;
    name?: string;
    vehicleModel?: string;
    currentMPG?: string;
  };
  return {
    _id: v._id,
    name: v.name ?? "",
    vehicleModel: v.vehicleModel ?? "",
    currentMPG: v.currentMPG ?? "",
  };
}

function mapTripSummary(trip: Record<string, unknown>, canEdit: boolean) {
  return {
    _id: trip._id,
    name: trip.name,
    startPoint: trip.startPoint,
    endPoint: trip.endPoint,
    scheduledDate: trip.scheduledDate,
    status: trip.status,
    imageUrl: trip.imageUrl ?? null,
    calculatedDistance: trip.calculatedDistance,
    calculatedMileage: trip.calculatedMileage,
    calculatedGasRequired: trip.calculatedGasRequired,
    calculatedMetrics: trip.calculatedMetrics,
    vehicle: mapVehicleSummary(trip.vehicleId),
    canEdit,
  };
}

export async function shareTrip(
  ownerId: string,
  tripId: string,
  sharedWithUserId: string,
): Promise<Record<string, unknown>> {
  if (ownerId === sharedWithUserId) {
    throw new Error("CANNOT_SELF_SHARE");
  }

  await assertNotBlocked(ownerId, sharedWithUserId);

  const connected = await isConnected(ownerId, sharedWithUserId);
  if (!connected) {
    throw new Error("NOT_CONNECTED");
  }

  const recipient = await UserModel.findOne({
    _id: new Types.ObjectId(sharedWithUserId),
    isDeleted: false,
    isBanned: false,
  })
    .select("_id fullName email image")
    .lean();
  if (!recipient) {
    throw new Error("RECIPIENT_NOT_FOUND");
  }

  const trip = await Trip.findOne({
    _id: new Types.ObjectId(tripId),
    userId: new Types.ObjectId(ownerId),
    isDeleted: false,
  })
    .populate("vehicleId", "name vehicleModel currentMPG")
    .lean();
  if (!trip) {
    throw new Error("TRIP_NOT_FOUND");
  }

  const existing = await TripShareModel.findOne({
    tripId: new Types.ObjectId(tripId),
    sharedWith: new Types.ObjectId(sharedWithUserId),
    status: "active",
  }).lean();
  if (existing) {
    throw new Error("ALREADY_SHARED");
  }

  // Revive a previously revoked share, or create new
  const revived = await TripShareModel.findOneAndUpdate(
    {
      tripId: new Types.ObjectId(tripId),
      sharedWith: new Types.ObjectId(sharedWithUserId),
      status: "revoked",
    },
    {
      $set: {
        status: "active",
        sharedBy: new Types.ObjectId(ownerId),
      },
    },
    { new: true },
  );

  const share =
    revived ??
    (await TripShareModel.create({
      tripId: new Types.ObjectId(tripId),
      sharedBy: new Types.ObjectId(ownerId),
      sharedWith: new Types.ObjectId(sharedWithUserId),
      status: "active",
    }));

  const owner = await UserModel.findById(ownerId).select("fullName email").lean();
  const ownerName = (owner?.fullName || owner?.email || "Someone").trim();
  void notify({
    userId: sharedWithUserId,
    type: "trip_share",
    eventType: "shared",
    title: "Trip shared with you",
    body: `${ownerName} shared a trip with you`,
    data: {
      shareId: String(share._id),
      tripId,
      sharedBy: ownerId,
      sharedWith: sharedWithUserId,
    },
  });

  return {
    _id: share._id,
    status: share.status,
    sharedWith: mapUserSummary(recipient),
    trip: mapTripSummary(trip as unknown as Record<string, unknown>, true),
    createdAt: share.createdAt,
  };
}

export async function getSharedTrips(
  userId: string,
  type: "received" | "sent",
  page: number,
  limit: number,
): Promise<PaginatedResult<Record<string, unknown>>> {
  const skip = (page - 1) * limit;
  const uid = new Types.ObjectId(userId);
  const query =
    type === "received"
      ? { sharedWith: uid, status: "active" as const }
      : { sharedBy: uid, status: "active" as const };

  const total = await TripShareModel.countDocuments(query);
  const shares = await TripShareModel.find(query)
    .sort({ createdAt: -1 })
    .skip(skip)
    .limit(limit)
    .lean();

  if (shares.length === 0) {
    return { data: [], total, page, totalPages: Math.ceil(total / limit) || 0 };
  }

  const tripIds = shares.map((s) => s.tripId);
  const userIds = [
    ...new Set(
      shares.flatMap((s) => [String(s.sharedBy), String(s.sharedWith)]),
    ),
  ].map((id) => new Types.ObjectId(id));

  const [trips, users] = await Promise.all([
    Trip.find({ _id: { $in: tripIds }, isDeleted: false })
      .populate("vehicleId", "name vehicleModel currentMPG")
      .lean(),
    UserModel.find({ _id: { $in: userIds }, isDeleted: false })
      .select("_id fullName email image")
      .lean(),
  ]);

  const tripMap = new Map(trips.map((t) => [String(t._id), t]));
  const userMap = new Map(users.map((u) => [String(u._id), u]));

  const data = shares
    .map((share) => {
      const trip = tripMap.get(String(share.tripId));
      if (!trip) return null;
      const canEdit = type === "sent";
      return {
        _id: share._id,
        status: share.status,
        sharedBy: mapUserSummary(userMap.get(String(share.sharedBy)) ?? null),
        sharedWith: mapUserSummary(
          userMap.get(String(share.sharedWith)) ?? null,
        ),
        trip: mapTripSummary(
          trip as unknown as Record<string, unknown>,
          canEdit,
        ),
        canEdit: type === "received" ? false : true,
        createdAt: share.createdAt,
      };
    })
    .filter((row): row is NonNullable<typeof row> => row !== null);

  return {
    data,
    total,
    page,
    totalPages: Math.ceil(total / limit) || 0,
  };
}

export async function getSharedTripById(
  shareId: string,
  userId: string,
): Promise<Record<string, unknown>> {
  const share = await TripShareModel.findOne({
    _id: new Types.ObjectId(shareId),
    status: "active",
  }).lean();
  if (!share) throw new Error("NOT_FOUND");

  const isParty =
    String(share.sharedBy) === userId || String(share.sharedWith) === userId;
  if (!isParty) throw new Error("FORBIDDEN");

  const trip = await Trip.findOne({
    _id: share.tripId,
    isDeleted: false,
  })
    .populate("vehicleId", "name vehicleModel currentMPG")
    .lean();
  if (!trip) throw new Error("TRIP_NOT_FOUND");

  const users = await UserModel.find({
    _id: { $in: [share.sharedBy, share.sharedWith] },
    isDeleted: false,
  })
    .select("_id fullName email image")
    .lean();
  const userMap = new Map(users.map((u) => [String(u._id), u]));

  const isOwner = String(share.sharedBy) === userId;
  const canEdit = isOwner;

  return {
    _id: share._id,
    status: share.status,
    sharedBy: mapUserSummary(userMap.get(String(share.sharedBy)) ?? null),
    sharedWith: mapUserSummary(userMap.get(String(share.sharedWith)) ?? null),
    trip: mapTripSummary(trip as unknown as Record<string, unknown>, canEdit),
    canEdit,
    createdAt: share.createdAt,
  };
}

export async function revokeTripShare(
  shareId: string,
  userId: string,
): Promise<void> {
  const share = await TripShareModel.findOne({
    _id: new Types.ObjectId(shareId),
    status: "active",
  });
  if (!share) throw new Error("NOT_FOUND");
  if (String(share.sharedBy) !== userId) throw new Error("FORBIDDEN");

  share.status = "revoked";
  await share.save();
}
