import { ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import type { SupabaseClient } from "@supabase/supabase-js";
import type { EventAndTablo } from "@xtablo/shared-types";
import type { Context, Next } from "hono";
import type { Transporter } from "nodemailer";
import type { StreamChat } from "stream-chat";
import { generatePassword } from "./token.js";

export const MAX_TABLO_LIMIT = 10;
export const MAX_TEAM_MEMBER_LIMIT = 3;

export type NormalizedPlan = "solo" | "team" | "annual";

const PLAN_WEIGHT: Record<NormalizedPlan, number> = {
  solo: 1,
  team: 2,
  annual: 3,
};

export const normalizePlan = (plan: string | null | undefined): NormalizedPlan => {
  if (!plan) return "solo";

  if (plan === "annual" || plan === "beta" || plan === "founder" || plan === "infinite") {
    return "annual";
  }
  if (plan === "team" || plan === "standard") return "team";
  return "solo";
};

export const getHighestPlan = (plans: Array<string | null | undefined>): NormalizedPlan => {
  const normalizedPlans = plans.map((plan) => normalizePlan(plan));
  if (normalizedPlans.length === 0) {
    return "solo";
  }

  return normalizedPlans.sort((a, b) => PLAN_WEIGHT[b] - PLAN_WEIGHT[a])[0]!;
};

export const getOrganizationPlan = async (
  supabase: SupabaseClient,
  organizationId: number
): Promise<{ plan: NormalizedPlan; error?: string }> => {
  const { data: plans, error } = await supabase
    .from("profiles")
    .select("plan")
    .eq("organization_id", organizationId);

  if (error) {
    return { plan: "solo", error: error.message };
  }

  return {
    plan: getHighestPlan((plans || []).map((profile) => profile.plan)),
  };
};

export const generateICSFromEvents = (
  events: EventAndTablo[],
  calendarName: string = "Planning"
) => {
  const formatDate = (date: string, time: string) => {
    // Combine date (YYYY-MM-DD) and time (HH:MM:SS) into ISO format then convert to UTC
    const dateTime = new Date(`${date}T${time}`);
    return `${dateTime.toISOString().replace(/[-:]/g, "").split(".")[0]}Z`;
  };

  const escapeICSText = (text: string) => {
    return text
      .replace(/\\/g, "\\\\")
      .replace(/;/g, "\\;")
      .replace(/,/g, "\\,")
      .replace(/\n/g, "\\n")
      .replace(/\r/g, "");
  };

  const generateUID = (eventId: string) => {
    return `${eventId}@xtablo.com`;
  };

  let icsContent = [
    "BEGIN:VCALENDAR",
    "VERSION:2.0",
    "PRODID:-//XTablo//Planning Export//EN",
    `X-WR-CALNAME:${escapeICSText(calendarName)}`,
    "X-WR-TIMEZONE:Europe/Paris",
    "CALSCALE:GREGORIAN",
    "METHOD:PUBLISH",
  ].join("\r\n");

  events.forEach((event) => {
    if (!event.start_date || !event.start_time || !event.title) return;

    const startDateTime = formatDate(event.start_date, event.start_time);
    const endDateTime = event.end_time
      ? formatDate(event.start_date, event.end_time)
      : formatDate(event.start_date, event.start_time); // Default to start time if no end time

    const eventLines = [
      "",
      "BEGIN:VEVENT",
      `UID:${generateUID(event.event_id)}`,
      `DTSTART:${startDateTime}`,
      `DTEND:${endDateTime}`,
      `SUMMARY:${escapeICSText(event.title)}`,
      `DESCRIPTION:${escapeICSText(`Tablo: ${event.tablo_name}\n${event.description || ""}`)}`,
      event.tablo_name ? `CATEGORIES:${escapeICSText(event.tablo_name)}` : "",
      `CREATED:${new Date().toISOString().replace(/[-:]/g, "").split(".")[0]}Z`,
      `LAST-MODIFIED:${new Date().toISOString().replace(/[-:]/g, "").split(".")[0]}Z`,
      "STATUS:CONFIRMED",
      "TRANSP:OPAQUE",
      "END:VEVENT",
    ].filter((line) => line !== ""); // Remove empty lines

    icsContent += `\r\n${eventLines.join("\r\n")}`;
  });

  icsContent += "\r\nEND:VCALENDAR";

  return icsContent;
};

export const writeCalendarFileToR2 = async (
  s3_client: S3Client,
  supabase: SupabaseClient,
  inputs: {
    token: string;
    tabloName: string;
    tablo_id: string;
  }
) => {
  const { token, tabloName, tablo_id } = inputs;

  const bucketName = "calendars";

  const key = `${token}/${tabloName}.ics`;

  const { data: events, error: eventsError } = await supabase
    .from("events_and_tablos")
    .select("*")
    .eq("tablo_id", tablo_id);

  if (eventsError || !events) {
    throw new Error("Failed to generate events");
  }

  const eventsData = events as EventAndTablo[];

  const icsContent = generateICSFromEvents(eventsData, tabloName);

  await s3_client.send(
    new PutObjectCommand({
      Bucket: bucketName,
      Key: key,
      Body: icsContent,
    })
  );
};

export const getTabloFileNames = async (s3_client: S3Client, tabloId: string) => {
  const bucketName = "tablo-data";

  const { Contents } = await s3_client.send(
    new ListObjectsV2Command({
      Bucket: bucketName,
      Prefix: `${tabloId}/`,
    })
  );

  // Return full file paths relative to tabloId (e.g., "file.pdf" or "folder-123/file.pdf")
  const prefix = `${tabloId}/`;
  return Contents?.map((content) => content.Key?.substring(prefix.length)).filter(
    (content) => content?.length && content.length > 0
  );
};

const isTabloMember = async (supabase: SupabaseClient, tabloId: string, userId: string) => {
  const { data: tabloAccess, error: isMemberError } = await supabase
    .from("tablo_access")
    .select("id")
    .eq("tablo_id", tabloId)
    .eq("user_id", userId)
    .eq("is_active", true)
    .maybeSingle();

  if (isMemberError) {
    return false;
  }

  return !!tabloAccess;
};

const isTabloAdmin = async (supabase: SupabaseClient, tabloId: string, userId: string) => {
  const { data: tabloAccess, error: isAdminError } = await supabase
    .from("tablo_access")
    .select("id")
    .eq("tablo_id", tabloId)
    .eq("user_id", userId)
    .eq("is_active", true)
    .eq("is_admin", true)
    .maybeSingle();
  // unique_tablo_access ensures at most one row

  if (isAdminError) {
    return false;
  }

  return !!tabloAccess;
};

export const checkTabloMember = async (c: Context, next: Next) => {
  const supabase = c.get("supabase");
  const user = c.get("user");
  const tabloId = c.req.param("tabloId");
  const isMember = await isTabloMember(supabase, tabloId, user.id);
  if (!isMember) {
    return c.json({ error: "You are not a member of this tablo" }, 403);
  }
  await next();
};

export const checkTabloAdmin = async (c: Context, next: Next) => {
  const supabase = c.get("supabase");
  const user = c.get("user");
  const tabloId = c.req.param("tabloId") || c.req.query("tablo_id");
  const isAdmin = await isTabloAdmin(supabase, tabloId, user.id);
  if (!isAdmin) {
    return c.json({ error: "You are not an admin of this tablo" }, 403);
  }
  await next();
};

export const verifyTabloLimitForUser = async (c: Context, next: Next) => {
  const supabase = c.get("supabase");
  const user = c.get("user");

  // Get user profile to check organization and subscription status
  const { data: profile, error: profileError } = await supabase
    .from("profiles")
    .select("organization_id")
    .eq("id", user.id)
    .single();

  if (profileError) {
    return c.json({ error: "Failed to get user profile" }, 500);
  }

  const userProfile = profile as { organization_id: number | null };

  if (!userProfile.organization_id) {
    return c.json({ error: "No organization found for current user" }, 400);
  }

  const { plan, error: planError } = await getOrganizationPlan(
    supabase,
    userProfile.organization_id
  );
  if (planError) {
    return c.json({ error: "Failed to resolve organization plan" }, 500);
  }

  if (plan === "solo") {
    const { count, error: countError } = await supabase
      .from("tablos")
      .select("id", { count: "exact" })
      .eq("organization_id", userProfile.organization_id)
      .is("deleted_at", null);

    const tabloCount = count as number;

    if (countError) {
      return c.json({ error: "Failed to check tablo count" }, 500);
    }

    if (tabloCount >= MAX_TABLO_LIMIT) {
      return c.json({ error: "You have reached your tablo limit" }, 403);
    }
    await next();
  } else {
    await next();
  }
};

/**
 * Creates a new invited user account and adds them to a tablo
 * @param supabase - Supabase client with admin privileges
 * @param transporter - Email transporter for sending welcome email
 * @param recipientEmail - Email address of the user to invite
 * @param senderEmail - Email of the user sending the invitation
 * @returns Object with success status and optional error message
 */
export const createInvitedUser = async (
  supabase: SupabaseClient,
  streamServerClient: StreamChat,
  transporter: Transporter,
  recipientEmail: string,
  senderEmail: string,
  options?: {
    isTemporary?: boolean;
  }
): Promise<{ success: boolean; error?: string; userId?: string }> => {
  const isTemporary = options?.isTemporary ?? true;
  const xtabloUrl = process.env.XTABLO_URL || "https://app.xtablo.com";

  // Create a new user account for the invited email
  const password = generatePassword();
  const { data: newUser, error: createUserError } = await supabase.auth.admin.createUser({
    email: recipientEmail,
    password: password,
    email_confirm: true,
    user_metadata: {
      name: recipientEmail.split("@")[0],
      first_name: recipientEmail.split("@")[0],
      last_name: "",
      role: "invited_user",
    },
    app_metadata: {
      // Can't do that: https://github.com/supabase/auth/issues/1280
      // role: "invited_user",
    },
  });

  if (createUserError) {
    console.error("Error creating user:", createUserError);
    return { success: false, error: createUserError.message };
  }

  const { error: updateProfileError } = await supabase
    .from("profiles")
    .update({ is_temporary: isTemporary })
    .eq("id", newUser.user.id);

  if (updateProfileError) {
    console.error("Error setting invited user temporary status:", updateProfileError);
    return { success: false, error: updateProfileError.message };
  }

  await streamServerClient.upsertUser({
    id: newUser.user.id,
    name: recipientEmail.split("@")[0],
    language: "fr",
  });

  // Send welcome email to the new user
  await transporter.sendMail({
    from: `${senderEmail} via XTablo <noreply@xtablo.com>`,
    to: recipientEmail,
    subject: "Vous avez été invité sur XTablo",
    html: `
<p>Bonjour !</p>

<p>${senderEmail} vous a invité à rejoindre XTablo.</p>

<p>Un nouveau compte a été créé pour vous avec les identifiants suivants :</p>
<div style="background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px; margin: 16px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
  <p style="margin: 0; font-family: monospace;"><strong>Email :</strong> ${recipientEmail}</p>
  <p style="margin: 8px 0 0 0; font-family: monospace;"><strong>Mot de passe :</strong> ${password}</p>
</div>

<p>Veuillez cliquer sur le lien ci-dessous pour accepter l'invitation et configurer votre mot de passe.</p>

<p><a href="${xtabloUrl}/login" style="display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; font-weight: bold;">Accepter et se connecter</a></p>

<p style="color: #d9534f; margin-bottom: 20px;"><strong>Important :</strong> Pour des raisons de sécurité, nous vous recommandons fortement de changer ce mot de passe temporaire lors de votre première connexion.</p>

<p style="color: #666; font-size: 14px; margin-top: 30px;">
  Cordialement,<br>
  L'équipe XTablo
</p>
    `,
  });

  return { success: true, userId: newUser.user.id };
};
