import { DeleteObjectsCommand, ListObjectsV2Command, PutObjectCommand } from "@aws-sdk/client-s3";
import type { Tables } from "@xtablo/shared-types";
import { Hono } from "hono";
import { createFactory } from "hono/factory";
import { getOrganizationBillingState } from "../helpers/billing.js";
import { createInvitedUser, getOrganizationPlan, MAX_TABLO_LIMIT } from "../helpers/helpers.js";
import type { AuthEnv } from "../types/app.types.js";

const factory = createFactory<AuthEnv>();
const isMissingRelationError = (code: string | undefined) =>
  code === "42P01" || code === "PGRST205";

const signUpToStream = factory.createHandlers(async (c) => {
  const { id } = c.get("user");
  const supabase = c.get("supabase");

  const { data } = await supabase.from("profiles").select("*").eq("id", id).single();

  const user = data as Tables<"profiles">;

  const streamServerClient = c.get("streamServerClient");
  await streamServerClient.upsertUser({
    id,
    name: user.name ?? "",
    language: "fr",
  });

  return c.json({
    message: "User signed up to stream",
  });
});

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

  const { data, error } = await supabase.from("profiles").select("*").eq("id", user.id).single();

  const userData = data as Tables<"profiles"> & {
    organization_id: number | null;
    plan: string | null;
  };

  if (!userData) {
    return c.json({ error: "User not found" }, 404);
  }

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

  let effectivePlan: string | null = userData.plan;
  if (userData.organization_id) {
    const { plan: organizationPlan } = await getOrganizationPlan(
      supabase,
      userData.organization_id
    );
    effectivePlan = organizationPlan;
  }

  const user_id = data.id;
  const token = streamServerClient.createToken(user_id);

  return c.json({
    ...userData,
    plan: effectivePlan,
    streamToken: token,
  });
});

const markTemporary = factory.createHandlers(async (c) => {
  const user = c.get("user");
  const supabase = c.get("supabase");

  const body = await c.req.json();
  const { temporary_password } = body;

  const { data: profile, error } = await supabase
    .from("profiles")
    .update({
      is_temporary: true,
    })
    .eq("id", user.id)
    .select()
    .single();

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

  const transporter = c.get("transporter");

  try {
    if (profile?.email && transporter) {
      const mailOptions = {
        from: "Xtablo <noreply@xtablo.com>",
        to: profile.email,
        subject: "Bienvenue sur XTablo - Votre mot de passe temporaire",
        text: `Bienvenue sur XTablo !

Votre compte a été créé avec succès. Voici vos informations de connexion :

Email : ${profile.email}
Mot de passe temporaire : ${temporary_password}

Pour des raisons de sécurité, nous vous recommandons fortement de changer ce mot de passe temporaire lors de votre première connexion.

Connectez-vous sur : ${process.env.FRONTEND_URL || "https://app.xtablo.com"}

Cordialement,
L'équipe XTablo`,
        html: `
          <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
            <h2 style="color: #333;">Bienvenue sur XTablo !</h2>
            
            <p>Votre compte a été créé avec succès. Voici vos informations de connexion :</p>
            
            <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0;">
              <p><strong>Email :</strong> ${profile.email}</p>
              <p><strong>Mot de passe temporaire :</strong> <code style="background-color: #e1e1e1; padding: 2px 4px; border-radius: 3px;">${temporary_password}</code></p>
            </div>
            
            <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>
              <a href="${process.env.FRONTEND_URL || "https://app.tablo.com"}" 
                 style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">
                Se connecter à XTablo
              </a>
            </p>
            
            <p style="color: #666; font-size: 14px; margin-top: 30px;">
              Cordialement,<br>
              L'équipe XTablo
            </p>
          </div>
        `,
      };
      await transporter.sendMail(mailOptions);
    }
  } catch (error) {
    console.error("Failed to send welcome email:", error);
  }

  return c.json({
    message: "User marked as temporary",
  });
});

// userRouter.put("/profile", async (c) => {
//   const user = c.get("user");
//   const supabase = c.get("supabase");

//   const body = await c.req.json();
//   const { first_name, last_name } = body;

//   // Deprecated: name field is deprecated, use first_name and last_name instead
//   // Combine first_name and last_name into a single name field
//   const name = [first_name, last_name].filter(Boolean).join(" ");

//   const updateData =
//     first_name && last_name
//       ? {
//           name,
//           first_name,
//           last_name,
//         }
//       : {};

//   const { data: profile, error } = await supabase
//     .from("profiles")
//     .update(updateData)
//     .eq("id", user.id)
//     .select()
//     .single();

//   if (error) {
//     return c.json({ error: error.message }, 500);
//   }

//   return c.json({
//     message: "Profile updated successfully",
//     profile,
//   });
// });

const uploadAvatar = factory.createHandlers(async (c) => {
  const user = c.get("user");
  const supabase = c.get("supabase");
  const s3Client = c.get("s3_client");

  const body = await c.req.json();
  const { content, contentType = "image/jpeg" } = body;

  if (!content) {
    return c.json({ error: "Content is required" }, 400);
  }

  const randomString = Math.random().toString(36).substring(2, 15);
  const base64Content = Buffer.from(content, "base64");
  const key = `${user.id}/public_avatar_${randomString}.${contentType.split("/")[1]}`;

  try {
    await s3Client.send(
      new PutObjectCommand({
        Bucket: "web-assets",
        Key: key,
        Body: base64Content,
        ContentType: contentType,
        ContentEncoding: "base64",
      })
    );
  } catch (error) {
    console.error("Failed to upload avatar:", error);
    return c.json({ error: "Failed to upload avatar" }, 500);
  }

  const avatarUrl = `https://assets.xtablo.com/${key}`;

  const { data, error } = await supabase
    .from("profiles")
    .update({ avatar_url: avatarUrl })
    .eq("id", user.id)
    .select()
    .single();

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

  return c.json({
    message: "Avatar updated successfully",
    profile: data,
  });
});

const deleteAvatar = factory.createHandlers(async (c) => {
  const user = c.get("user");
  const supabase = c.get("supabase");
  const s3Client = c.get("s3_client");

  try {
    const listedObjects = await s3Client.send(
      new ListObjectsV2Command({
        Bucket: "web-assets",
        Prefix: `${user.id}/`,
      })
    );

    if (listedObjects.Contents.length === 0) return c.json({ error: "No objects found" }, 404);

    await s3Client.send(
      new DeleteObjectsCommand({
        Bucket: "web-assets",
        Delete: { Objects: listedObjects.Contents.map(({ Key }) => ({ Key })) },
      })
    );
  } catch (error) {
    console.error("Failed to delete avatar:", error);
    return c.json({ error: "Failed to delete avatar" }, 500);
  }

  const { error } = await supabase
    .from("profiles")
    .update({ avatar_url: null })
    .eq("id", user.id)
    .select()
    .single();

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

  return c.json({
    message: "Avatar deleted successfully",
  });
});

const getOrganization = factory.createHandlers(async (c) => {
  const user = c.get("user");
  const supabase = c.get("supabase");

  const { data: profile, error: profileError } = await supabase
    .from("profiles")
    .select("organization_id")
    .eq("id", user.id)
    .single();

  if (profileError || !profile?.organization_id) {
    return c.json({ error: "Failed to resolve your organization" }, 500);
  }

  const organizationId = profile.organization_id;

  const { data: organization, error: organizationError } = await supabase
    .from("organizations")
    .select("id, name")
    .eq("id", organizationId)
    .single();

  if (organizationError || !organization) {
    return c.json({ error: "Organization not found" }, 404);
  }

  const { data: members, error: membersError } = await supabase
    .from("profiles")
    .select("id, email, name, first_name, last_name, avatar_url, created_at, is_temporary, plan")
    .eq("organization_id", organizationId)
    .order("created_at", { ascending: true });

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

  const { count: tabloCount, error: tabloCountError } = await supabase
    .from("tablos")
    .select("id", { count: "exact", head: true })
    .eq("organization_id", organizationId)
    .is("deleted_at", null);

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

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

  const membersWithEffectivePlan = (members || []).map((member) => ({
    ...member,
    plan,
  }));

  const { data: billingState, error: billingError } = await getOrganizationBillingState(
    supabase,
    organizationId
  );
  if (billingError || !billingState) {
    return c.json({ error: "Failed to resolve organization billing state" }, 500);
  }

  let invitesSent: Array<{
    id: number;
    invited_email: string;
    invited_user_id: string | null;
    created_at: string;
    invited_member: {
      id: string;
      email: string | null;
      name: string | null;
      first_name: string | null;
      last_name: string | null;
      avatar_url: string | null;
    } | null;
  }> = [];

  const { data: invitesData, error: invitesError } = await supabase
    .from("organization_invites")
    .select(
      `
      id,
      invited_email,
      invited_user_id,
      created_at,
      invited_member:profiles!organization_invites_invited_user_id_fkey(
        id,
        email,
        name,
        first_name,
        last_name,
        avatar_url
      )
    `
    )
    .eq("organization_id", organizationId)
    .eq("invited_by", user.id)
    .order("created_at", { ascending: false });

  if (invitesError && !isMissingRelationError(invitesError.code)) {
    return c.json({ error: "Failed to load organization invites" }, 500);
  }

  if (!invitesError && invitesData) {
    invitesSent = invitesData.map((invite) => {
      const invitedMemberRaw = invite.invited_member as
        | {
            id: string;
            email: string | null;
            name: string | null;
            first_name: string | null;
            last_name: string | null;
            avatar_url: string | null;
          }
        | {
            id: string;
            email: string | null;
            name: string | null;
            first_name: string | null;
            last_name: string | null;
            avatar_url: string | null;
          }[]
        | null;

      return {
        id: invite.id as number,
        invited_email: (invite.invited_email as string) ?? "",
        invited_user_id: (invite.invited_user_id as string | null) ?? null,
        created_at: (invite.created_at as string) ?? new Date().toISOString(),
        invited_member: Array.isArray(invitedMemberRaw)
          ? (invitedMemberRaw[0] ?? null)
          : invitedMemberRaw,
      };
    });
  }

  return c.json({
    organization: {
      id: organization.id,
      name: organization.name,
      plan,
      member_count: members?.length || 0,
      tablo_count: tabloCount || 0,
    },
    members: membersWithEffectivePlan,
    trial_starts_at: billingState.trial_starts_at,
    trial_ends_at: billingState.trial_ends_at,
    is_trial_expired: billingState.is_trial_expired,
    required_plan: billingState.required_plan,
    required_team_quantity: billingState.required_team_quantity,
    active_subscription_plan: billingState.active_subscription_plan,
    active_subscription_quantity: billingState.active_subscription_quantity,
    is_billing_owner: billingState.owner_user_id === user.id,
    invites_sent: invitesSent,
  });
});

const updateOrganization = factory.createHandlers(async (c) => {
  const user = c.get("user");
  const supabase = c.get("supabase");
  const body = await c.req.json();
  const rawName = typeof body?.name === "string" ? body.name : "";
  const name = rawName.trim();

  if (name.length < 2 || name.length > 100) {
    return c.json({ error: "Organization name must be between 2 and 100 characters" }, 400);
  }

  const { data: profile, error: profileError } = await supabase
    .from("profiles")
    .select("organization_id, is_temporary")
    .eq("id", user.id)
    .single();

  if (profileError || !profile?.organization_id) {
    return c.json({ error: "Failed to resolve your organization" }, 500);
  }

  if (profile.is_temporary) {
    return c.json({ error: "Temporary users cannot update organization settings" }, 403);
  }

  const { error: updateError } = await supabase
    .from("organizations")
    .update({ name })
    .eq("id", profile.organization_id);

  if (updateError) {
    return c.json({ error: updateError.message }, 500);
  }

  return c.json({ message: "Organization updated successfully" });
});

const inviteToOrganization = factory.createHandlers(async (c) => {
  const user = c.get("user");
  const supabase = c.get("supabase");
  const transporter = c.get("transporter");
  const streamServerClient = c.get("streamServerClient");
  const body = await c.req.json();
  const rawEmail = typeof body?.email === "string" ? body.email : "";
  const recipientEmail = rawEmail.trim().toLowerCase();

  if (!recipientEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipientEmail)) {
    return c.json({ error: "A valid email is required" }, 400);
  }

  const { data: senderProfile, error: senderError } = await supabase
    .from("profiles")
    .select("organization_id, email, is_temporary")
    .eq("id", user.id)
    .single();

  if (senderError || !senderProfile?.organization_id || !senderProfile?.email) {
    return c.json({ error: "Failed to resolve your organization" }, 500);
  }

  if (senderProfile.is_temporary) {
    return c.json({ error: "Temporary users cannot invite organization members" }, 403);
  }

  if (senderProfile.email.toLowerCase() === recipientEmail) {
    return c.json({ error: "You cannot invite yourself" }, 400);
  }

  const organizationId = senderProfile.organization_id;
  const { data: billingState, error: billingError } = await getOrganizationBillingState(
    supabase,
    organizationId
  );
  if (billingError || !billingState) {
    return c.json({ error: "Failed to resolve organization billing state" }, 500);
  }

  const { data: existingUser, error: existingUserError } = await supabase
    .from("profiles")
    .select("id, organization_id")
    .eq("email", recipientEmail)
    .maybeSingle();

  if (existingUserError) {
    return c.json({ error: existingUserError.message }, 500);
  }

  if (existingUser) {
    if (existingUser.organization_id === organizationId) {
      return c.json({ message: "User is already in your organization" });
    }

    return c.json({ error: "This email already belongs to another organization" }, 409);
  }

  if (billingState.is_trial_expired && billingState.active_subscription_plan !== "annual") {
    const requiredSeatsForInvite = billingState.member_count + 1;

    if (billingState.active_subscription_plan === "team") {
      if (billingState.active_subscription_quantity < requiredSeatsForInvite) {
        return c.json(
          {
            error:
              "Your Teams subscription does not have enough seats for this invite. Please increase seats in billing and try again.",
          },
          403
        );
      }
    } else {
      const noSubscriptionMessage =
        billingState.required_plan === "solo"
          ? "Your trial has ended. Solo allows 1 member only. Upgrade to Teams to invite more members."
          : "Your trial has ended. An active Teams subscription is required to invite members. Please subscribe or increase seats in billing.";

      return c.json(
        {
          error: noSubscriptionMessage,
        },
        403
      );
    }
  }

  const invitedUser = await createInvitedUser(
    supabase,
    streamServerClient,
    transporter,
    recipientEmail,
    senderProfile.email,
    { isTemporary: false }
  );

  if (!invitedUser.success || !invitedUser.userId) {
    return c.json({ error: invitedUser.error || "Failed to create invited user" }, 500);
  }

  const { data: createdProfile, error: createdProfileError } = await supabase
    .from("profiles")
    .select("organization_id")
    .eq("id", invitedUser.userId)
    .single();

  if (createdProfileError) {
    return c.json({ error: "Invited user was created, but profile update failed" }, 500);
  }

  const oldOrganizationId = createdProfile.organization_id;

  const { error: assignOrganizationError } = await supabase
    .from("profiles")
    .update({ organization_id: organizationId, is_temporary: false })
    .eq("id", invitedUser.userId);

  if (assignOrganizationError) {
    return c.json({ error: "Failed to assign invited user to your organization" }, 500);
  }

  const { data: organizationTablos, error: organizationTablosError } = await supabase
    .from("tablos")
    .select("id")
    .eq("organization_id", organizationId)
    .is("deleted_at", null);

  if (organizationTablosError) {
    return c.json({ error: "Failed to sync organization access" }, 500);
  }

  if ((organizationTablos || []).length > 0) {
    const accessRows = (organizationTablos || []).map((tablo) => ({
      tablo_id: tablo.id,
      user_id: invitedUser.userId!,
      granted_by: user.id,
      is_active: true,
      is_admin: true,
    }));

    const { error: accessError } = await supabase.from("tablo_access").upsert(accessRows, {
      onConflict: "tablo_id,user_id",
    });

    if (accessError) {
      return c.json({ error: "Failed to sync invited user tablo permissions" }, 500);
    }
  }

  for (const tablo of organizationTablos || []) {
    const channel = streamServerClient.channel("messaging", tablo.id);
    try {
      await channel.addMembers([invitedUser.userId]);
    } catch (error) {
      console.error("Failed to add invited user to Stream channel:", error);
    }
  }

  if (oldOrganizationId && oldOrganizationId !== organizationId) {
    const { count: oldOrgMembersCount } = await supabase
      .from("profiles")
      .select("id", { count: "exact", head: true })
      .eq("organization_id", oldOrganizationId);

    if ((oldOrgMembersCount || 0) === 0) {
      await supabase.from("organizations").delete().eq("id", oldOrganizationId);
    }
  }

  const { error: inviteHistoryError } = await supabase.from("organization_invites").insert({
    organization_id: organizationId,
    invited_by: user.id,
    invited_email: recipientEmail,
    invited_user_id: invitedUser.userId,
  });

  if (inviteHistoryError && !isMissingRelationError(inviteHistoryError.code)) {
    console.error("Failed to store organization invite history:", inviteHistoryError);
  }

  return c.json({
    message: "Invitation sent successfully",
    limits: {
      plan: billingState.active_subscription_plan ?? billingState.required_plan,
      max_tablos_for_solo: MAX_TABLO_LIMIT,
      required_team_quantity_for_next_invite: billingState.member_count + 1,
    },
  });
});

const removeOrganizationMember = factory.createHandlers(async (c) => {
  const user = c.get("user");
  const supabase = c.get("supabase");
  const streamServerClient = c.get("streamServerClient");
  const memberId = c.req.param("memberId");

  if (!memberId) {
    return c.json({ error: "Member id is required" }, 400);
  }

  const { data: actorProfile, error: actorProfileError } = await supabase
    .from("profiles")
    .select("organization_id, is_temporary")
    .eq("id", user.id)
    .single();

  if (actorProfileError || !actorProfile?.organization_id) {
    return c.json({ error: "Failed to resolve your organization" }, 500);
  }

  if (actorProfile.is_temporary) {
    return c.json({ error: "Temporary users cannot manage organization members" }, 403);
  }

  const organizationId = actorProfile.organization_id;
  const { data: billingState, error: billingError } = await getOrganizationBillingState(
    supabase,
    organizationId
  );

  if (billingError || !billingState) {
    return c.json({ error: "Failed to resolve organization billing state" }, 500);
  }

  if (billingState.owner_user_id !== user.id) {
    return c.json({ error: "Only the organization creator can remove members" }, 403);
  }

  if (memberId === billingState.owner_user_id) {
    return c.json({ error: "The organization creator cannot be removed" }, 400);
  }

  const { data: memberProfile, error: memberProfileError } = await supabase
    .from("profiles")
    .select("id, email, name, first_name, last_name, organization_id")
    .eq("id", memberId)
    .maybeSingle();

  if (memberProfileError) {
    return c.json({ error: memberProfileError.message }, 500);
  }

  if (!memberProfile || memberProfile.organization_id !== organizationId) {
    return c.json({ error: "Member not found in your organization" }, 404);
  }

  const baseName =
    [memberProfile.first_name, memberProfile.last_name].filter(Boolean).join(" ").trim() ||
    memberProfile.name?.trim() ||
    memberProfile.email?.split("@")[0]?.trim() ||
    "Personal";

  const { data: newOrganization, error: newOrganizationError } = await supabase
    .from("organizations")
    .insert({ name: `${baseName}'s Workspace` })
    .select("id")
    .single();

  if (newOrganizationError || !newOrganization) {
    return c.json({ error: "Failed to create a workspace for this member" }, 500);
  }

  const { error: assignError } = await supabase
    .from("profiles")
    .update({ organization_id: newOrganization.id })
    .eq("id", memberId);

  if (assignError) {
    return c.json({ error: "Failed to remove member from organization" }, 500);
  }

  const { error: transferOwnershipError } = await supabase
    .from("tablos")
    .update({ owner_id: user.id })
    .eq("organization_id", organizationId)
    .eq("owner_id", memberId);

  if (transferOwnershipError) {
    return c.json({ error: "Failed to transfer ownership for member tablos" }, 500);
  }

  const { data: organizationTablos, error: tablosError } = await supabase
    .from("tablos")
    .select("id")
    .eq("organization_id", organizationId);

  if (tablosError) {
    return c.json({ error: "Failed to synchronize organization access" }, 500);
  }

  const tabloIds = (organizationTablos || []).map((tablo) => tablo.id);
  if (tabloIds.length > 0) {
    const { error: removeAccessError } = await supabase
      .from("tablo_access")
      .delete()
      .eq("user_id", memberId)
      .in("tablo_id", tabloIds);

    if (removeAccessError) {
      return c.json({ error: "Failed to revoke member tablo permissions" }, 500);
    }

    for (const tabloId of tabloIds) {
      try {
        const channel = streamServerClient.channel("messaging", tabloId);
        await channel.removeMembers([memberId]);
      } catch (error) {
        console.error("Failed to remove organization member from Stream channel:", error);
      }
    }
  }

  const { error: inviteCleanupError } = await supabase
    .from("organization_invites")
    .delete()
    .eq("organization_id", organizationId)
    .eq("invited_user_id", memberId);

  if (inviteCleanupError && !isMissingRelationError(inviteCleanupError.code)) {
    console.error("Failed to clean organization invite history:", inviteCleanupError);
  }

  return c.json({ message: "Member removed successfully" });
});

export const getUserRouter = () => {
  const userRouter = new Hono();

  userRouter.post("/sign-up-to-stream", ...signUpToStream);
  userRouter.get("/me", ...getMe);
  userRouter.post("/mark-temporary", ...markTemporary);
  userRouter.post("/profile/avatar", ...uploadAvatar);
  userRouter.delete("/profile/avatar", ...deleteAvatar);
  userRouter.get("/organization", ...getOrganization);
  userRouter.patch("/organization", ...updateOrganization);
  userRouter.post("/organization/invite", ...inviteToOrganization);
  userRouter.delete("/organization/members/:memberId", ...removeOrganizationMember);

  return userRouter;
};
