import type { Database, TablesInsert } from "@xtablo/shared-types";
import { Hono } from "hono";
import { createFactory } from "hono/factory";
import { createInvitedUser, getOrganizationPlan, MAX_TABLO_LIMIT } from "../helpers/helpers.js";
import type { EventTypeConfig } from "../helpers/slots.js";
import type { MaybeAuthEnv } from "../types/app.types.js";

const factory = createFactory<MaybeAuthEnv>();

const bookSlot = factory.createHandlers(async (c) => {
  const supabase = c.get("supabase");
  const streamServerClient = c.get("streamServerClient");
  const transporter = c.get("transporter");
  const maybeUser = c.get("user");

  const data = await c.req.json();

  // Validate that owner_id is provided
  if (!data.owner_short_id) {
    return c.json({ error: "owner_id is required" }, 400);
  }

  if (!data.event_type_standard_name) {
    return c.json({ error: "event is required" }, 400);
  }

  // TODO: Verify that the owner_id is correct
  const { data: ownerData, error: ownerError } = await supabase
    .from("profiles")
    .select("id, name, email, organization_id")
    .eq("short_user_id", data.owner_short_id)
    .single();

  if (ownerError || !ownerData) {
    console.error("Error fetching owner:", ownerError);
    return c.json({ error: "owner_id is incorrect" }, 400);
  }

  let hasCreatedAccount = false;
  if (!maybeUser) {
    // Check if email already exists in the database
    const { data: existingUser, error: existingUserError } = await supabase
      .from("profiles")
      .select("id, email")
      .eq("email", data.user_details.email)
      .maybeSingle();

    if (existingUserError) {
      console.error("Error checking existing user:", existingUserError);
      return c.json({ error: "Failed to check existing user" }, 500);
    }

    if (!existingUser) {
      hasCreatedAccount = true;
      // Create a temporary user for the booking
      const result = await createInvitedUser(
        supabase,
        streamServerClient,
        transporter,
        data.user_details.email,
        ownerData.email,
        { isTemporary: true }
      );

      if (!result.success) {
        console.error("Error creating invited user:", result.error);
        return c.json({ error: result.error }, 500);
      }
    }
  }

  const { data: bookerUser, error: bookerUserError } = await supabase
    .from("profiles")
    .select("id, name, email")
    .eq("email", data.user_details.email)
    .maybeSingle();

  if (bookerUserError) {
    console.error("Error fetching booker user:", bookerUserError);
    return c.json({ error: "Failed to get booker user" }, 500);
  }

  const ownerDataTyped = ownerData as {
    id: string;
    name: string;
    email: string;
    organization_id: number | null;
  };
  const ownerId = ownerDataTyped.id;
  const ownerOrganizationId = ownerDataTyped.organization_id;

  if (!ownerOrganizationId) {
    return c.json({ error: "Owner organization not found" }, 500);
  }

  const { plan: organizationPlan, error: organizationPlanError } = await getOrganizationPlan(
    supabase,
    ownerOrganizationId
  );

  if (organizationPlanError) {
    return c.json({ error: "Failed to resolve organization plan" }, 500);
  }

  const bookerUserDataTyped = bookerUser as {
    id: string;
    name: string;
    email: string;
  };

  if (ownerDataTyped.email === bookerUserDataTyped.email) {
    return c.json({ error: "You cannot create a tablo with yourself" }, 400);
  }

  const { data: eventTypeData, error: eventTypeError } = await supabase
    .from("event_types")
    .select("*")
    .eq("user_id", ownerId)
    .eq("standard_name", data.event_type_standard_name)
    .is("deleted_at", null)
    .single();

  if (eventTypeError || !eventTypeData) {
    console.error("Error fetching event type:", eventTypeError);
    return c.json({ error: "Event type not found" }, 404);
  }

  const eventType = eventTypeData as Database["public"]["Tables"]["event_types"]["Row"];
  const eventTypeConfig = eventType.config as EventTypeConfig;

  // TODO: Verify that the event start and end correspond to a slot

  // Check if there's already a tablo between the owner and the invited user
  const { data: existingTablo, error: existingTabloError } = await supabase
    .from("tablos")
    .select(
      `
      id,
      name,
      organization_id,
      tablo_access!inner(user_id)
    `
    )
    .eq("organization_id", ownerOrganizationId)
    .eq("tablo_access.user_id", bookerUserDataTyped.id)
    .is("deleted_at", null)
    .limit(1);

  if (existingTabloError) {
    console.error("existingTabloError", existingTabloError);
    return c.json({ error: existingTabloError.message }, 500);
  }

  let tabloData: { id: string; name: string } | null = null;

  if (!existingTablo.length) {
    if (organizationPlan === "solo") {
      const { count: tabloCount, error: tabloCountError } = await supabase
        .from("tablos")
        .select("id", { count: "exact", head: true })
        .eq("organization_id", ownerOrganizationId)
        .is("deleted_at", null);

      if (tabloCountError) {
        return c.json({ error: "Failed to validate organization tablo limit" }, 500);
      }

      if ((tabloCount || 0) >= MAX_TABLO_LIMIT) {
        return c.json(
          {
            error:
              "Your organization has reached the Solo plan tablo limit. Upgrade your plan to create more tablos.",
          },
          403
        );
      }
    }

    // Create the tablo with the specified owner
    const { data: insertedTablo, error } = await supabase
      .from("tablos")
      .insert({
        name: `${bookerUserDataTyped.name || "Invité"} / ${ownerDataTyped.name || "Propriétaire"}`,
        color: "bg-blue-500",
        status: "todo",
        owner_id: ownerId,
        organization_id: ownerOrganizationId,
      })
      .select()
      .single();

    if (error) {
      console.error("Error creating tablo:", error);
      return c.json({ error: error.message }, 500);
    }

    tabloData = insertedTablo as { id: string; name: string };
  } else {
    tabloData = existingTablo[0] as { id: string; name: string };
  }

  // Grant access to the current user (invited user) as a non-admin member
  const { error: tabloAccessError } = await supabase.from("tablo_access").upsert(
    {
      tablo_id: tabloData.id,
      user_id: bookerUserDataTyped.id,
      // ** IMPORTANT **
      is_admin: false,
      // -------------
      is_active: true,
      granted_by: ownerId,
    },
    {
      onConflict: "tablo_id, user_id",
    }
  );

  if (tabloAccessError) {
    console.error("tabloAccessError", tabloAccessError);
    return c.json({ error: tabloAccessError.message }, 500);
  }

  // Create Stream chat channel with the owner as creator
  const { data: organizationMembers, error: organizationMembersError } = await supabase
    .from("profiles")
    .select("id")
    .eq("organization_id", ownerOrganizationId);

  if (organizationMembersError) {
    return c.json({ error: "Failed to load organization members" }, 500);
  }

  const channelMembers = Array.from(
    new Set((organizationMembers || []).map((member) => member.id).concat(bookerUserDataTyped.id))
  );

  const channel = streamServerClient.channel("messaging", tabloData.id, {
    // @ts-ignore
    name: tabloData.name,
    created_by_id: ownerId,
    members: channelMembers,
  });
  await channel.create();

  const newEvent: TablesInsert<"events"> = {
    description: eventTypeConfig.description || "",
    end_time: data.event_details.end_time || "",
    start_date: data.event_details.start_date || "",
    start_time: data.event_details.start_time || "",
    title: eventTypeConfig.name || "",
    tablo_id: tabloData.id,
    created_by: ownerId,
  };

  const { error: eventError } = await supabase.from("events").insert(newEvent);
  if (eventError) {
    console.error("Error creating event:", eventError);
    return c.json({ error: "Failed to create event" }, 500);
  }

  // Send a welcome message to the channel
  await channel.sendMessage({
    text: `🎉 Bienvenue dans votre nouveau tablo "${tabloData.name}" ! Votre rendez-vous "${newEvent.title}" est confirmé pour le ${newEvent.start_date} de ${newEvent.start_time} à ${newEvent.end_time}.`,
    user_id: ownerId,
  });

  // Send email notifications to both owner and invited user
  // Send email to the owner
  await transporter.sendMail({
    from: "Xtablo <noreply@xtablo.com>",
    to: ownerDataTyped.email,
    subject: "Nouveau tablo créé - Réservation confirmée",
    html: `
        <h2>Votre tablo a été créé avec succès !</h2>
        <p>Bonjour ${ownerDataTyped.name},</p>
        <p>Un nouveau tablo "${tabloData.name}" a été créé suite à une réservation.</p>
        <p><strong>Détails de l'événement :</strong></p>
        <ul>
          <li>Titre : ${newEvent.title}</li>
          <li>Date : ${newEvent.start_date}</li>
          <li>Heure : ${newEvent.start_time} - ${newEvent.end_time}</li>
          <li>Description : ${newEvent.description}</li>
        </ul>
        <p>Participant : ${bookerUserDataTyped.name} (${bookerUserDataTyped.email})</p>
        <p>Vous pouvez gérer ce tablo depuis votre tableau de bord.</p>
      `,
  });

  // Send email to the invited user
  await transporter.sendMail({
    from: "Xtablo <noreply@xtablo.com>",
    to: bookerUserDataTyped.email,
    subject: "Réservation confirmée - Nouveau tablo créé",
    html: `
        <h2>Votre réservation est confirmée !</h2>
        <p>Bonjour ${bookerUserDataTyped.name},</p>
        <p>Votre réservation a été confirmée et un tablo "${tabloData.name}" a été créé.</p>
        <p><strong>Détails de votre rendez-vous :</strong></p>
        <ul>
          <li>Titre : ${newEvent.title}</li>
          <li>Date : ${newEvent.start_date}</li>
          <li>Heure : ${newEvent.start_time} - ${newEvent.end_time}</li>
          <li>Description : ${newEvent.description}</li>
        </ul>
        <p>Avec : ${ownerDataTyped.name}</p>
        <p>Vous recevrez bientôt plus d'informations pour accéder à votre espace de collaboration.</p>
      `,
  });

  return c.json({
    message: "Booking successful",
    tablo_id: tabloData.id,
    hasCreatedAccount,
    email: bookerUserDataTyped.email,
  });
});

export const getBookingRouter = () => {
  const bookingRouter = new Hono();

  bookingRouter.post("/slot", ...bookSlot);

  return bookingRouter;
};
