import type { FastifyReply, FastifyRequest } from "fastify";

import { AppError } from "../lib/errors.js";
import { clearSessionCookies, setSessionCookies } from "../lib/session.js";
import { createAuthClient, supabaseAdmin } from "../lib/supabase.js";
import { uniqueSlug } from "../lib/utils.js";

export async function requireUser(request: FastifyRequest) {
  const user = request.authContext.user;

  if (!user) {
    throw new AppError(401, "UNAUTHORIZED", "You must be signed in to access this resource.");
  }

  return user;
}

export async function requireProfile(request: FastifyRequest) {
  const user = await requireUser(request);
  const profile = request.authContext.profile;

  if (!profile) {
    throw new AppError(404, "PROFILE_NOT_FOUND", "No user profile is available for this account.");
  }

  return { user, profile };
}

export async function requireOrganizationMembership(request: FastifyRequest, organizationId?: string | null) {
  const { user } = await requireProfile(request);
  const activeOrganizationId = organizationId ?? request.authContext.organizationId;

  if (!activeOrganizationId) {
    throw new AppError(400, "ORGANIZATION_REQUIRED", "An organization must be selected.");
  }

  const { data, error } = await supabaseAdmin
    .from("organization_members")
    .select("id, role, organization_id")
    .eq("organization_id", activeOrganizationId)
    .eq("auth_user_id", user.id)
    .maybeSingle();

  if (error || !data) {
    throw new AppError(403, "ORGANIZATION_ACCESS_DENIED", "You do not have access to this organization.");
  }

  return data;
}

export async function requirePlatformAdmin(request: FastifyRequest) {
  const { profile } = await requireProfile(request);

  if (profile.role !== "platform_admin") {
    throw new AppError(403, "FORBIDDEN", "Platform admin access is required.");
  }

  return profile;
}

export async function signUpUser(input: {
  email: string;
  password: string;
  fullName: string;
  organizationName: string;
}) {
  const normalizedEmail = input.email.trim().toLowerCase();

  const existing = await supabaseAdmin.auth.admin.listUsers({
    page: 1,
    perPage: 200
  });

  if (existing.data.users.some((user) => user.email?.toLowerCase() === normalizedEmail)) {
    throw new AppError(409, "EMAIL_ALREADY_EXISTS", "An account already exists for this email address.");
  }

  const createdUser = await supabaseAdmin.auth.admin.createUser({
    email: normalizedEmail,
    password: input.password,
    email_confirm: true,
    user_metadata: {
      full_name: input.fullName
    }
  });

  if (createdUser.error || !createdUser.data.user) {
    throw new AppError(400, "SIGNUP_FAILED", createdUser.error?.message ?? "Unable to create user.");
  }

  const authUser = createdUser.data.user;
  const organizationSlug = uniqueSlug(input.organizationName);

  const { data: organization, error: organizationError } = await supabaseAdmin
    .from("organizations")
    .insert({
      name: input.organizationName.trim(),
      slug: organizationSlug
    })
    .select("id")
    .single();

  if (organizationError || !organization) {
    throw new AppError(500, "ORGANIZATION_CREATE_FAILED", organizationError?.message ?? "Unable to create organization.");
  }

  const { error: profileError } = await supabaseAdmin.from("user_profiles").insert({
    auth_user_id: authUser.id,
    email: normalizedEmail,
    full_name: input.fullName.trim(),
    role: "user",
    default_organization_id: organization.id
  });

  if (profileError) {
    throw new AppError(500, "PROFILE_CREATE_FAILED", profileError.message);
  }

  const { error: membershipError } = await supabaseAdmin.from("organization_members").insert({
    organization_id: organization.id,
    auth_user_id: authUser.id,
    role: "owner"
  });

  if (membershipError) {
    throw new AppError(500, "MEMBERSHIP_CREATE_FAILED", membershipError.message);
  }

  const { data: freePlan } = await supabaseAdmin
    .from("plan_definitions")
    .select("id")
    .eq("code", "free")
    .single();

  if (freePlan?.id) {
    await supabaseAdmin.from("organization_subscriptions").insert({
      organization_id: organization.id,
      plan_definition_id: freePlan.id,
      status: "active"
    });
  }

  await supabaseAdmin.from("brand_kits").upsert(
    {
      organization_id: organization.id,
      primary_color: "#0f766e",
      secondary_color: "#0f172a",
      accent_color: "#14b8a6"
    },
    {
      onConflict: "organization_id"
    }
  );

  const authClient = createAuthClient();
  const signIn = await authClient.auth.signInWithPassword({
    email: normalizedEmail,
    password: input.password
  });

  if (signIn.error || !signIn.data.session) {
    throw new AppError(500, "SIGNIN_AFTER_SIGNUP_FAILED", signIn.error?.message ?? "Account created but session could not be started.");
  }

  return {
    session: signIn.data.session,
    organizationId: organization.id
  };
}

export async function signInUser(input: { email: string; password: string }) {
  const authClient = createAuthClient();
  const signIn = await authClient.auth.signInWithPassword({
    email: input.email.trim().toLowerCase(),
    password: input.password
  });

  if (signIn.error || !signIn.data.session) {
    throw new AppError(401, "INVALID_CREDENTIALS", signIn.error?.message ?? "Invalid email or password.");
  }

  const { data: profile } = await supabaseAdmin
    .from("user_profiles")
    .select("default_organization_id")
    .eq("auth_user_id", signIn.data.session.user.id)
    .single();

  return {
    session: signIn.data.session,
    organizationId: profile?.default_organization_id ?? null
  };
}

export async function sendPasswordReset(email: string) {
  const authClient = createAuthClient();
  const result = await authClient.auth.resetPasswordForEmail(email.trim().toLowerCase(), {
    redirectTo: `${process.env.APP_BASE_URL}/reset-password`
  });

  if (result.error) {
    throw new AppError(400, "PASSWORD_RESET_FAILED", result.error.message);
  }
}

export async function resetPassword(input: {
  accessToken: string;
  refreshToken: string;
  password: string;
}) {
  const authClient = createAuthClient();
  const setSession = await authClient.auth.setSession({
    access_token: input.accessToken,
    refresh_token: input.refreshToken
  });

  if (setSession.error || !setSession.data.session) {
    throw new AppError(400, "INVALID_RECOVERY_SESSION", setSession.error?.message ?? "The recovery session is invalid.");
  }

  const update = await authClient.auth.updateUser({
    password: input.password
  });

  if (update.error || !update.data.user) {
    throw new AppError(400, "PASSWORD_UPDATE_FAILED", update.error?.message ?? "Unable to update password.");
  }

  return setSession.data.session;
}

export async function signOutUser(request: FastifyRequest, reply: FastifyReply) {
  const refreshToken = request.authContext.refreshToken;

  if (refreshToken) {
    const authClient = createAuthClient();
    await authClient.auth.refreshSession({
      refresh_token: refreshToken
    });
    await authClient.auth.signOut();
  }

  clearSessionCookies(reply);
}

export function applySession(reply: FastifyReply, session: { access_token: string; refresh_token: string; expires_in?: number }, organizationId?: string | null) {
  setSessionCookies(reply, session, organizationId);
}
