import {
  type AnyRoleName,
  archestraApiSdk,
  type archestraApiTypes,
} from "@archestra/shared";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { Invitation } from "better-auth/plugins/organization";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { useSession } from "@/lib/auth/auth.query";
import { authClient } from "@/lib/clients/auth/auth-client";
import { environmentKeys } from "./environment.query";
import { handleApiError, throwOnApiError } from "./utils";

export const appearanceKeys = {
  all: ["appearance"] as const,
  public: () => [...appearanceKeys.all, "public"] as const,
};

/**
 * Hook to fetch public appearance settings.
 * Used on login/auth pages where the user is not yet authenticated.
 * Returns theme, customFont, and logo without requiring authentication.
 * On API failure the query enters its error state (no toast, since this is a
 * pre-auth surface); callers keep using their local fallback appearance values.
 */
export function useAppearanceSettings(enabled = true) {
  return useQuery({
    queryKey: appearanceKeys.public(),
    queryFn: async () => {
      const { data, error } = await archestraApiSdk.getAppearanceSettings();
      throwOnApiError(error, { toastOnError: false });
      return data ?? null;
    },
    enabled,
    staleTime: 5 * 60 * 1000,
    retry: false,
    throwOnError: false,
  });
}

/**
 * Query key factory for organization-related queries
 */
export const organizationKeys = {
  all: ["organization"] as const,
  invitations: () => [...organizationKeys.all, "invitations"] as const,
  invitation: (id: string) => [...organizationKeys.invitations(), id] as const,
  activeOrg: () => [...organizationKeys.all, "active"] as const,
  activeMemberRole: () =>
    [...organizationKeys.activeOrg(), "member-role"] as const,
  details: () => [...organizationKeys.all, "details"] as const,
  onboardingStatus: () =>
    [...organizationKeys.all, "onboarding-status"] as const,
  memberSignupStatus: () =>
    [...organizationKeys.all, "member-signup-status"] as const,
};

/**
 * Fetch invitation details by ID
 */
export function useInvitation(invitationId: string) {
  const session = useSession();
  return useQuery({
    queryKey: organizationKeys.invitation(invitationId),
    queryFn: async () => {
      const response = await authClient.organization.getInvitation({
        query: { id: invitationId },
      });
      return response.data;
    },
    enabled: !!session.data?.user,
  });
}

/**
 * Use active organization from authClient hook
 * Note: This uses the authClient hook directly as it's already optimized
 */
export function useActiveOrganization() {
  return authClient.useActiveOrganization();
}

/**
 * Fetch active member role
 */
export function useActiveMemberRole(organizationId?: string) {
  return useQuery({
    queryKey: organizationKeys.activeMemberRole(),
    queryFn: async () => {
      const { data } = await authClient.organization.getActiveMemberRole();
      return data?.role;
    },
    enabled: !!organizationId,
  });
}

/**
 * Accept invitation mutation
 */
export function useAcceptInvitation() {
  const router = useRouter();
  return useMutation({
    mutationFn: async (invitationId: string) => {
      const response = await authClient.organization.acceptInvitation({
        invitationId,
      });
      return response.data;
    },
    onSuccess: () => {
      router.push("/");
    },
    onError: (error) => {
      // Extract the error message from the error object
      const errorMessage =
        error?.message ||
        (error as { error?: { message: string } })?.error?.message ||
        "Failed to accept invitation";

      toast.error("Error", {
        description: errorMessage,
      });
    },
  });
}

/**
 * List all pending invitations for an organization
 */
export function useInvitationsList(organizationId: string | undefined) {
  return useQuery({
    queryKey: [...organizationKeys.invitations(), organizationId],
    queryFn: async () => {
      if (!organizationId) return [];

      const response = await authClient.organization.listInvitations({
        query: { organizationId },
      });

      if (!response.data) return [];

      const now = new Date();
      type InvitationListItem = {
        id: string;
        email: string;
        role: Invitation["role"];
        expiresAt: Invitation["expiresAt"] | null;
        isExpired: boolean;
        status: Invitation["status"];
      };
      return response.data
        .filter((inv: Invitation) => inv.status === "pending")
        .map((inv: Invitation) => {
          const expiresAt = inv.expiresAt || null;
          const isExpired = expiresAt ? new Date(expiresAt) < now : false;

          return {
            id: inv.id,
            email: inv.email,
            role: inv.role,
            expiresAt,
            isExpired,
            status: inv.status,
          };
        })
        .sort((a: InvitationListItem, b: InvitationListItem) => {
          // Sort by status first (pending > accepted > rejected)
          const statusOrder: Record<string, number> = {
            pending: 0,
            accepted: 1,
            rejected: 2,
          };
          const statusDiff = statusOrder[a.status] - statusOrder[b.status];
          if (statusDiff !== 0) return statusDiff;

          // Then by expiry
          if (a.isExpired !== b.isExpired) {
            return a.isExpired ? 1 : -1;
          }
          return 0;
        });
    },
  });
}

/**
 * Delete invitation mutation
 */
export function useCancelInvitation() {
  return useMutation({
    mutationFn: async (invitationId: string) => {
      const response = await authClient.organization.cancelInvitation({
        invitationId,
      });
      return response.data;
    },
    onSuccess: () => {
      toast.success("Invitation deleted");
    },
    onError: (error) => {
      toast.error("Failed to delete invitation", {
        description: error.message,
      });
    },
  });
}

/**
 * Create invitation mutation
 */
export function useCreateInvitation(organizationId: string | undefined) {
  return useMutation({
    mutationFn: async ({
      email,
      role,
    }: {
      email: string;
      role: AnyRoleName;
    }) => {
      const response = await authClient.organization.inviteMember({
        email,
        /**
         * TODO: it looks like better-auth authClient has strict typing here..
         * and apparently, according to their docs, it can only be "owner", "admin", or "member".
         * https://www.better-auth.com/docs/plugins/organization#send-invitation
         */
        role: role as NonNullable<
          Parameters<typeof authClient.organization.inviteMember>[0]
        >["role"],
        organizationId,
      });

      if (response.error) {
        toast.error(
          response.error.message || "Failed to generate invitation link",
        );
        return null;
      }

      return response.data;
    },
    onSuccess: () => {
      toast.success("Invitation link generated", {
        description: "Share this link with the person you want to invite",
      });
    },
  });
}

/**
 * Get organization
 */
export function useOrganization(enabled = true) {
  const session = useSession();

  return useQuery({
    queryKey: organizationKeys.details(),
    queryFn: async () => {
      const { data, error } = await archestraApiSdk.getOrganization();
      throwOnApiError(error, { toastOnError: false });
      return data;
    },
    // Only fetch when user is authenticated to prevent 403 errors during initial auth check
    enabled: enabled && !!session.data?.user,
    retry: false, // Don't retry on auth pages to avoid repeated 401 errors
    throwOnError: false, // Don't throw errors to prevent crashes
    // Org settings (theme, app name, etc.) change rarely and all mutations
    // imperatively setQueryData() this key, so a long stale time keeps
    // re-mounts cheap.
    staleTime: 5 * 60 * 1000,
  });
}

/**
 * Check if organization onboarding is complete
 * Only polls when enabled
 */
export function useOrganizationOnboardingStatus(enabled: boolean) {
  return useQuery({
    queryKey: organizationKeys.onboardingStatus(),
    queryFn: async () => {
      const { data, error } = await archestraApiSdk.getOnboardingStatus();

      throwOnApiError(error);

      return (
        data ?? {
          hasProfilesConfigured: false,
          hasToolsConfigured: false,
          isComplete: false,
        }
      );
    },
    refetchInterval: enabled ? 3000 : false, // Poll every 3 seconds when dialog is open
    enabled, // Only run query when enabled
  });
}

/**
 * Update appearance settings
 */
export function useUpdateAppearanceSettings(
  onSuccessMessage: string,
  onErrorMessage: string,
) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async (
      data: archestraApiTypes.UpdateAppearanceSettingsData["body"],
    ) => {
      const { data: updatedOrganization, error } =
        await archestraApiSdk.updateAppearanceSettings({ body: data });

      if (error) {
        toast.error(onErrorMessage);
        return null;
      }

      return updatedOrganization;
    },
    onSuccess: (updatedOrganization, variables) => {
      if (!updatedOrganization) return;
      queryClient.setQueryData(organizationKeys.details(), updatedOrganization);
      queryClient.setQueryData(appearanceKeys.public(), {
        theme: updatedOrganization.theme,
        customFont: updatedOrganization.customFont,
        logo: updatedOrganization.logo,
        logoDark: updatedOrganization.logoDark,
        favicon: updatedOrganization.favicon,
        iconLogo: updatedOrganization.iconLogo,
        iconLogoDark: updatedOrganization.iconLogoDark,
        appName: updatedOrganization.appName,
        ogDescription: updatedOrganization.ogDescription,
        footerText: updatedOrganization.footerText,
        chatLinks: updatedOrganization.chatLinks,
        onboardingWizard: updatedOrganization.onboardingWizard,
        chatErrorSupportMessage: updatedOrganization.chatErrorSupportMessage,
        slimChatErrorUi: updatedOrganization.slimChatErrorUi,
        animateChatPlaceholders: updatedOrganization.animateChatPlaceholders,
      });
      // The app name is baked into the built-in skills' and tools' names on the
      // backend, so a rename re-brands those rows. Drop their cached lists so the
      // Skills/Tools pages show the new name without a manual page refresh.
      if (variables.appName !== undefined) {
        queryClient.invalidateQueries({ queryKey: ["skills"] });
        queryClient.invalidateQueries({ queryKey: ["tools"] });
      }
      toast.success(onSuccessMessage);
    },
  });
}

/**
 * Update security settings (default tool guardrails, chat file uploads)
 */
export function useUpdateSecuritySettings(
  onSuccessMessage: string,
  onErrorMessage: string,
) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async (
      data: archestraApiTypes.UpdateSecuritySettingsData["body"],
    ) => {
      const { data: updatedOrganization, error } =
        await archestraApiSdk.updateSecuritySettings({ body: data });

      if (error) {
        toast.error(onErrorMessage);
        return null;
      }

      return updatedOrganization;
    },
    onSuccess: (updatedOrganization) => {
      if (!updatedOrganization) return;
      queryClient.setQueryData(organizationKeys.details(), updatedOrganization);
      queryClient.invalidateQueries({ queryKey: ["config"] });
      toast.success(onSuccessMessage);
    },
  });
}

/**
 * Update LLM settings (TOON compression, compression scope, limit cleanup interval)
 */
export function useUpdateLlmSettings(
  onSuccessMessage: string,
  onErrorMessage: string,
) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async (
      data: archestraApiTypes.UpdateLlmSettingsData["body"],
    ) => {
      const { data: updatedOrganization, error } =
        await archestraApiSdk.updateLlmSettings({ body: data });

      if (error) {
        toast.error(onErrorMessage);
        return null;
      }

      return updatedOrganization;
    },
    onSuccess: (updatedOrganization) => {
      if (!updatedOrganization) return;
      queryClient.setQueryData(organizationKeys.details(), updatedOrganization);
      toast.success(onSuccessMessage);
    },
  });
}

/**
 * Update agent settings (default model, default agent)
 */
export function useUpdateAgentSettings(
  onSuccessMessage: string,
  onErrorMessage: string,
) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async (
      data: archestraApiTypes.UpdateAgentSettingsData["body"],
    ) => {
      const { data: updatedOrganization, error } =
        await archestraApiSdk.updateAgentSettings({ body: data });

      if (error) {
        toast.error(onErrorMessage);
        return null;
      }

      return updatedOrganization;
    },
    onSuccess: (updatedOrganization) => {
      if (!updatedOrganization) return;
      queryClient.setQueryData(organizationKeys.details(), updatedOrganization);
      toast.success(onSuccessMessage);
    },
  });
}

/**
 * Update /connection admin settings (default gateway/proxy, hidden client/provider lists)
 */
export function useUpdateConnectionSettings(
  onSuccessMessage: string,
  onErrorMessage: string,
) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async (
      data: archestraApiTypes.UpdateConnectionSettingsData["body"],
    ) => {
      const { data: updatedOrganization, error } =
        await archestraApiSdk.updateConnectionSettings({ body: data });

      if (error) {
        toast.error(onErrorMessage);
        return null;
      }

      return updatedOrganization;
    },
    onSuccess: (updatedOrganization) => {
      if (!updatedOrganization) return;
      queryClient.setQueryData(organizationKeys.details(), updatedOrganization);
      toast.success(onSuccessMessage);
    },
  });
}

/**
 * Update the org-wide default environment (the implicit "Default" target that
 * catalog items use when no environment is assigned). Unlike real environments,
 * the default has no slug, so both its name and namespace are freely editable.
 * Pass `name`/`namespace` (or null to reset to the built-in "Default").
 */
export function useUpdateDefaultEnvironment(
  onSuccessMessage: string,
  onErrorMessage: string,
) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async (
      data: archestraApiTypes.UpdateDefaultEnvironmentData["body"],
    ) => {
      const { data: updatedOrganization, error } =
        await archestraApiSdk.updateDefaultEnvironment({ body: data });

      if (error) {
        toast.error(onErrorMessage);
        return null;
      }

      return updatedOrganization;
    },
    onSuccess: (updatedOrganization) => {
      if (!updatedOrganization) return;
      queryClient.setQueryData(organizationKeys.details(), updatedOrganization);
      queryClient.invalidateQueries({ queryKey: environmentKeys.list() });
      toast.success(onSuccessMessage);
    },
  });
}

/**
 * Returns the org-configured default environment fields. When unconfigured,
 * `name` falls back to "Default", nullable fields fall back to null, and
 * `restricted` falls back to false.
 */
export function useDefaultEnvironment() {
  const { data: organization } = useOrganization();
  return {
    name: organization?.defaultEnvironmentName ?? "Default",
    namespace: organization?.defaultEnvironmentNamespace ?? null,
    description: organization?.defaultEnvironmentDescription ?? null,
    networkPolicy: organization?.defaultNetworkPolicy ?? null,
    restricted: organization?.defaultEnvironmentRestricted ?? false,
    validationRegex: organization?.defaultEnvironmentValidationRegex ?? null,
    trustedImageRegistries:
      organization?.defaultEnvironmentTrustedImageRegistries ?? null,
  };
}

/**
 * Update Auth settings (OAuth access token lifetime)
 */
export function useUpdateAuthSettings(
  onSuccessMessage: string,
  onErrorMessage: string,
) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async (
      data: archestraApiTypes.UpdateAuthSettingsData["body"],
    ) => {
      const { data: updatedOrganization, error } =
        await archestraApiSdk.updateAuthSettings({ body: data });

      if (error) {
        toast.error(onErrorMessage);
        return null;
      }

      return updatedOrganization;
    },
    onSuccess: (updatedOrganization) => {
      if (!updatedOrganization) return;
      queryClient.setQueryData(organizationKeys.details(), updatedOrganization);
      toast.success(onSuccessMessage);
    },
  });
}

/**
 * Update knowledge settings (embedding model)
 */
export function useUpdateKnowledgeSettings(
  onSuccessMessage: string,
  onErrorMessage: string,
) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async (
      data: archestraApiTypes.UpdateKnowledgeSettingsData["body"],
    ) => {
      const { data: updatedOrganization, error } =
        await archestraApiSdk.updateKnowledgeSettings({ body: data });

      if (error) {
        toast.error(onErrorMessage);
        return null;
      }

      return updatedOrganization;
    },
    onSuccess: (updatedOrganization) => {
      if (!updatedOrganization) return;
      queryClient.setQueryData(organizationKeys.details(), updatedOrganization);
      toast.success(onSuccessMessage);
    },
  });
}

/**
 * Drop embedding configuration (deletes all KB documents, resets connector checkpoints)
 */
export function useDropEmbeddingConfig() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async () => {
      const { data: updatedOrganization, error } =
        await archestraApiSdk.dropEmbeddingConfig();

      if (error) {
        handleApiError(error);
        return null;
      }

      return updatedOrganization;
    },
    onSuccess: (updatedOrganization) => {
      if (!updatedOrganization) return;
      queryClient.setQueryData(organizationKeys.details(), updatedOrganization);
      toast.success("Embedding configuration dropped");
    },
  });
}

/**
 * Test embedding connection by embedding a sample text
 */
export function useTestEmbeddingConnection() {
  return useMutation({
    mutationFn: async (
      params: NonNullable<
        archestraApiTypes.TestEmbeddingConnectionData["body"]
      >,
    ) => {
      const { data, error } = await archestraApiSdk.testEmbeddingConnection({
        body: params,
      });

      if (error) {
        handleApiError(error);
        return { success: false, error: "Request failed" };
      }

      return data ?? { success: false, error: "No response" };
    },
    onSuccess: (result) => {
      if (!result) return;
      if (result.success) {
        toast.success("Connection test successful");
      } else {
        toast.error("Connection test failed", {
          description: result.error,
        });
      }
    },
  });
}

/**
 * Get all members of the organization (for admin filtering)
 */
export function useOrganizationMembers(enabled = true) {
  return useQuery({
    queryKey: [...organizationKeys.all, "members"],
    queryFn: async () => {
      const { data, error } = await archestraApiSdk.getOrganizationMembers();
      throwOnApiError(error);
      return data ?? [];
    },
    enabled,
  });
}

export type PendingSignupMember = {
  userId: string;
  name: string | null;
  email: string;
  image: string | null;
  role: string;
  provider: string | null;
  invitationId: string | null;
};

/**
 * Get member signup status — returns members that haven't completed signup
 */
export function useMemberSignupStatus() {
  return useQuery({
    queryKey: organizationKeys.memberSignupStatus(),
    queryFn: async () => {
      const { data, error } = await archestraApiSdk.getMemberSignupStatus();
      throwOnApiError(error, { toastOnError: false });
      return data ?? { pendingSignupMembers: [] as PendingSignupMember[] };
    },
  });
}

/**
 * Delete a pending signup member (auto-provisioned, hasn't completed signup)
 */
export function useDeletePendingSignupMember() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async (userId: string) => {
      const { data, error } = await archestraApiSdk.deletePendingSignupMember({
        path: { userId },
      });
      if (error) {
        handleApiError(error);
        return null;
      }
      return data;
    },
    onSuccess: (data) => {
      if (!data) return;
      queryClient.invalidateQueries({
        queryKey: organizationKeys.memberSignupStatus(),
      });
      toast.success("Pending member removed");
    },
  });
}
