const API_ORIGIN_PROXY_PREFIX = '/idp-api';
const configuredApiUrl = (process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000').replace(/\/$/, '');
const configuredWsUrl = (process.env.NEXT_PUBLIC_WS_URL ?? 'ws://localhost:8085/ws').replace(/\/$/, '');

function resolveBrowserApiBaseUrl(): string {
  if (typeof window === 'undefined') {
    return configuredApiUrl;
  }

  try {
    const configured = new URL(configuredApiUrl);
    if (configured.hostname !== window.location.hostname) {
      return API_ORIGIN_PROXY_PREFIX;
    }
  } catch {
    // keep configured URL
  }

  return configuredApiUrl;
}

function resolveBrowserWsBaseUrl(): string {
  if (typeof window === 'undefined') {
    return configuredWsUrl;
  }

  const apiBase = resolveBrowserApiBaseUrl();
  if (apiBase === API_ORIGIN_PROXY_PREFIX) {
    const scheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    return `${scheme}//${window.location.host}${API_ORIGIN_PROXY_PREFIX}/ws`;
  }

  try {
    const configured = new URL(configuredWsUrl);
    if (configured.hostname !== window.location.hostname) {
      const scheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
      return `${scheme}//${window.location.host}${API_ORIGIN_PROXY_PREFIX}/ws`;
    }
  } catch {
    // keep configured URL
  }

  return configuredWsUrl;
}

export function getApiUrl(): string {
  return resolveBrowserApiBaseUrl();
}

export function getWsUrl(): string {
  return resolveBrowserWsBaseUrl();
}

/** @deprecated Используйте getApiUrl() — URL может переключаться на same-origin /idp-api в браузере */
export const API_URL = configuredApiUrl;

export interface PublicUser {
  id: string;
  email?: string | null;
  phone?: string | null;
  backupEmail?: string | null;
  backupPhone?: string | null;
  displayName: string;
  username?: string | null;
  avatarUrl?: string | null;
  hasAvatar?: boolean;
  isSuperAdmin: boolean;
  status: string;
  roles?: string[];
  permissions?: string[];
  canAccessAdmin?: boolean;
  canManageRoles?: boolean;
  canViewOAuth?: boolean;
  canManageOAuth?: boolean;
  canManageAllOAuth?: boolean;
  canManageUsers?: boolean;
  canManageAllUsers?: boolean;
  canManageSettings?: boolean;
  canViewUsers?: boolean;
  canViewUserDocuments?: boolean;
  hasPassword?: boolean;
  isVerified?: boolean;
  verificationIcon?: string | null;
  canVerifyUsers?: boolean;
  canManageBots?: boolean;
}

export interface AdminUser {
  id: string;
  email?: string | null;
  phone?: string | null;
  displayName: string;
  username?: string | null;
  isSuperAdmin: boolean;
  status: string;
  createdAt: string;
  roles: string[];
  directPermissions?: string[];
  isVerified?: boolean;
  verificationIcon?: string | null;
  isBot?: boolean;
  linkedBotUsername?: string | null;
  isSystemBot?: boolean;
}

export interface AdminRole {
  id: string;
  slug: string;
  name: string;
  description?: string;
  isSystem?: boolean;
  isDefault?: boolean;
  permissions: { id: string; slug: string; name: string; description?: string }[];
}

export interface AdminPermission {
  id: string;
  slug: string;
  name: string;
  description?: string;
}

export interface OAuthScope {
  id: string;
  slug: string;
  name: string;
  description?: string;
}

export interface OAuthConsentScopeInfo {
  slug: string;
  name: string;
  description?: string;
}

export interface OAuthConsentCheckResponse {
  granted: boolean;
  client?: {
    clientId: string;
    name: string;
  };
  requestedScopes?: OAuthConsentScopeInfo[];
}

export interface OAuthUserConsent {
  id: string;
  clientId: string;
  clientName: string;
  scopes: OAuthConsentScopeInfo[];
  grantedAt: string;
  updatedAt: string;
}

export interface OAuthClient {
  id: string;
  name: string;
  clientId: string;
  redirectUris: string[];
  scopes: string[];
  isActive: boolean;
  type: string;
  clientSecret?: string;
  createdById?: string | null;
  createdByDisplayName?: string | null;
  canManage?: boolean;
}

export interface AuthTokens {
  accessToken: string;
  refreshToken: string;
  expiresAt: string;
  pinVerified: boolean;
  user: PublicUser;
  sessionId: string;
  requiresTotp?: boolean;
  totpChallengeToken?: string;
}

export interface UserProfileResponse {
  birthDate?: string | null;
  email?: string | null;
  phone?: string | null;
  backupEmail?: string | null;
  backupPhone?: string | null;
  displayName?: string;
  firstName?: string | null;
  lastName?: string | null;
}

export interface QrLoginSession {
  sessionId: string;
  status: 'PENDING' | 'APPROVED' | 'EXPIRED';
  expiresAt: string;
  qrPayload?: string;
  auth?: AuthTokens & { user?: PublicUser };
}

export async function createQrLoginSession(deviceName: string) {
  return apiFetch<QrLoginSession>('/auth/advanced/qr/session', {
    method: 'POST',
    body: JSON.stringify({
      deviceName,
      fingerprint: getDeviceFingerprint(),
      deviceType: 'WEB'
    })
  });
}

export async function pollQrLoginSession(sessionId: string) {
  return apiFetch<QrLoginSession>(`/auth/advanced/qr/session/${sessionId}`);
}

export interface TotpSetupResponse {
  secret: string;
  otpauthUrl: string;
}

export interface TotpStatusResponse {
  isEnabled: boolean;
}

export interface PresignedUploadResponse {
  uploadUrl: string;
  storageKey: string;
  expiresAt?: string;
  uploadToken?: string;
}

export async function uploadMediaObject(
  presigned: PresignedUploadResponse,
  file: Blob,
  contentType: string
) {
  if (presigned.uploadToken) {
    const apiBase = getApiUrl().replace(/\/$/, '');
    const formData = new FormData();
    formData.append('file', file, file instanceof File ? file.name : 'upload.bin');
    const response = await fetch(`${apiBase}/media/upload`, {
      method: 'POST',
      headers: { 'X-Upload-Token': presigned.uploadToken },
      body: formData
    });
    if (!response.ok) {
      throw new Error('Не удалось загрузить файл');
    }
    return;
  }

  const response = await fetch(presigned.uploadUrl, {
    method: 'PUT',
    headers: { 'Content-Type': contentType },
    body: file
  });
  if (!response.ok) {
    throw new Error('Не удалось загрузить файл');
  }
}

export interface FamilyInviteCandidate {
  id: string;
  displayName: string;
  email?: string | null;
  phone?: string | null;
  username?: string | null;
  hasAvatar?: boolean;
  isVerified?: boolean;
  verificationIcon?: string | null;
  isBot?: boolean;
  botUsername?: string;
  botId?: string;
  ownerDisplayName?: string;
}

export async function searchFamilyInviteUsers(groupId: string, query: string, token?: string | null) {
  return apiFetch<{ users?: FamilyInviteCandidate[] }>(
    `/family/groups/${groupId}/invite-search?q=${encodeURIComponent(query)}`,
    {},
    token
  );
}

export interface PasswordlessAuthResponse {
  requiresPassword: boolean;
  requiresTotp?: boolean;
  totpChallengeToken?: string;
  tempAuthToken?: string;
  auth?: AuthTokens;
}

export interface OtpChannel {
  channel: 'email' | 'phone' | 'backupEmail' | 'backupPhone' | string;
  masked: string;
}

export interface LoginMethod {
  kind: 'password' | 'otp';
  channel: string;
  masked: string;
}

export interface IdentifyResponse {
  exists: boolean;
  hasPassword: boolean;
  isPinEnabled: boolean;
  isTotpEnabled?: boolean;
  otpChannels?: OtpChannel[];
  methods?: LoginMethod[];
}

export interface BeginTotpLoginResponse {
  totpChallengeToken: string;
}

export interface OtpSendResponse {
  sent: boolean;
  expiresAt: string;
  maskedTarget: string;
  resendAvailableAt?: string;
}

export interface PinVerificationResponse {
  accessToken: string;
  pinVerified: boolean;
}

export interface RefreshSessionResponse {
  requiresPin: boolean;
  accessToken?: string;
  sessionId?: string;
  pinVerified: boolean;
  user?: PublicUser;
}

export interface AuthSessionResponse {
  requiresPin: boolean;
  sessionId?: string | null;
  pinVerified: boolean;
  user: PublicUser;
}

export const AUTH_TOKEN_KEY = 'lendry_access_token';
export const AUTH_REFRESH_KEY = 'lendry_refresh_token';
export const AUTH_SESSION_KEY = 'lendry_session_id';
export const AUTH_USER_CACHE_KEY = 'lendry_cached_user';

export class ApiError extends Error {
  status: number;
  code?: string;
  sessionId?: string;

  constructor(message: string, status: number, code?: string, sessionId?: string) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
    this.code = code;
    this.sessionId = sessionId;
  }
}

function mapFetchError(error: unknown): ApiError {
  if (error instanceof TypeError) {
    const message = error.message.toLowerCase();
    if (message.includes('failed to fetch') || message.includes('networkerror')) {
      return new ApiError(
        'Нет связи с сервером. При самоподписанном HTTPS примите сертификат сайта или выполните ./install.sh --fix-proxy на сервере.',
        0,
        'NETWORK_ERROR'
      );
    }
  }
  if (error instanceof ApiError) {
    return error;
  }
  return new ApiError(error instanceof Error ? error.message : 'Не удалось выполнить запрос', 0, 'UNKNOWN');
}

type PinRequiredHandler = (sessionId: string) => void;

export function setPinRequiredHandler(handler: PinRequiredHandler | null) {
  pinRequiredHandler = handler;
  if (!handler) {
    pinNotificationSent = false;
  }
}

interface ErrorBody {
  message?: string | string[];
  error?: string;
  code?: string;
  sessionId?: string;
}

async function parseErrorResponse(response: Response): Promise<ApiError> {
  try {
    const body = (await response.clone().json()) as ErrorBody;
    const message = Array.isArray(body.message)
      ? body.message.join('\n')
      : (body.message ?? body.error ?? 'Не удалось выполнить запрос');
    const pinRequired =
      body.code === 'PIN_REQUIRED' ||
      (response.status === 403 && typeof message === 'string' && message.toLowerCase().includes('pin'));
    return new ApiError(message, response.status, pinRequired ? 'PIN_REQUIRED' : body.code, body.sessionId);
  } catch {
    return new ApiError('Не удалось выполнить запрос', response.status);
  }
}

export function isPinRequiredError(error: unknown): error is ApiError {
  return error instanceof ApiError && error.code === 'PIN_REQUIRED';
}

export function getApiErrorMessage(error: unknown, fallback: string): string | null {
  if (isPinRequiredError(error)) return null;
  if (error instanceof ApiError && error.status === 403 && error.message.toLowerCase().includes('pin')) {
    return null;
  }
  return error instanceof Error ? error.message : fallback;
}

let pinRequiredHandler: PinRequiredHandler | null = null;
let pinNotificationSent = false;

export function resetPinRequiredNotification() {
  pinNotificationSent = false;
}

function notifyPinRequired(sessionId: string) {
  if (!pinRequiredHandler || pinNotificationSent) return;
  pinNotificationSent = true;
  pinRequiredHandler(sessionId);
}

export async function fetchAuthSession(token?: string | null): Promise<AuthSessionResponse> {
  return apiFetch<AuthSessionResponse>('/auth/session', {}, token);
}

export async function approveOAuthAuthorization(params: URLSearchParams, token: string) {
  const body = {
    client_id: params.get('client_id') ?? params.get('clientId') ?? undefined,
    redirect_uri: params.get('redirect_uri') ?? params.get('redirectUri') ?? undefined,
    scope: params.get('scope') ?? 'openid profile',
    state: params.get('state') ?? undefined,
    nonce: params.get('nonce') ?? undefined
  };
  return apiFetch<{ redirectUrl?: string }>('/oauth/consent/approve', {
    method: 'POST',
    headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  }, token);
}

export async function checkOAuthConsent(params: URLSearchParams, token: string) {
  const query = new URLSearchParams();
  const clientId = params.get('client_id') ?? params.get('clientId');
  const scope = params.get('scope') ?? 'openid profile';
  if (clientId) query.set('client_id', clientId);
  query.set('scope', scope);
  return apiFetch<OAuthConsentCheckResponse>(`/oauth/consent/check?${query.toString()}`, {}, token);
}

export async function fetchOAuthClientPublicInfo(clientId: string) {
  return apiFetch<{ clientId: string; name: string }>(`/oauth/clients/${encodeURIComponent(clientId)}/public`);
}

export async function fetchUserOAuthConsents(userId: string, token: string) {
  return apiFetch<{ consents?: OAuthUserConsent[] }>(`/oauth/consents/users/${userId}`, {}, token);
}

export async function revokeOAuthConsent(userId: string, consentId: string, token: string) {
  return apiFetch(`/oauth/consents/users/${userId}/${consentId}`, { method: 'DELETE' }, token);
}

export async function refreshAuthSession(): Promise<RefreshSessionResponse> {
  if (typeof window === 'undefined') {
    throw new ApiError('Refresh token недоступен на сервере', 401, 'NO_REFRESH');
  }

  const refreshToken = window.localStorage.getItem(AUTH_REFRESH_KEY);
  const sessionId = window.localStorage.getItem(AUTH_SESSION_KEY);
  if (!refreshToken) {
    throw new ApiError('Refresh token отсутствует', 401, 'NO_REFRESH');
  }

  const response = await fetch(`${getApiUrl()}/auth/refresh`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refreshToken, sessionId: sessionId ?? undefined })
  });

  if (!response.ok) {
    throw await parseErrorResponse(response);
  }

  return response.json() as Promise<RefreshSessionResponse>;
}

async function trySilentTokenRefresh(): Promise<RefreshSessionResponse | null> {
  try {
    const refreshed = await refreshAuthSession();
    if (refreshed.requiresPin) {
      notifyPinRequired(refreshed.sessionId ?? window.localStorage.getItem(AUTH_SESSION_KEY) ?? '');
      return refreshed;
    }
    if (refreshed.accessToken) {
      window.localStorage.setItem(AUTH_TOKEN_KEY, refreshed.accessToken);
      if (refreshed.sessionId) {
        window.localStorage.setItem(AUTH_SESSION_KEY, refreshed.sessionId);
      }
      if (refreshed.user) {
        window.dispatchEvent(new CustomEvent('lendry:auth-refreshed', { detail: refreshed }));
      }
    }
    return refreshed;
  } catch {
    return null;
  }
}

export interface UserDocument {
  id: string;
  userId: string;
  type: string;
  number: string;
  issuedAt?: string;
  expiresAt?: string;
  metadataJson?: string;
}

export interface SystemSetting {
  id: string;
  key: string;
  value: string;
  description?: string | null;
  isSecret: boolean;
}

export interface SocialProvider {
  id: string;
  providerName: string;
  clientId: string;
  isEnabled: boolean;
}

export interface ActiveDevice {
  id: string;
  name: string;
  type: string;
  lastIp?: string | null;
  lastSeenAt: string;
  trusted: boolean;
}

export interface SignInEvent {
  id: string;
  success: boolean;
  reason?: string | null;
  ipAddress?: string | null;
  userAgent?: string | null;
  createdAt: string;
}

export interface ActiveSession {
  id: string;
  userId: string;
  deviceId?: string | null;
  pinVerified: boolean;
  status: string;
  ipAddress?: string | null;
  userAgent?: string | null;
  expiresAt: string;
  createdAt: string;
  updatedAt: string;
}

async function parseError(response: Response): Promise<string> {
  const error = await parseErrorResponse(response);
  return error.message;
}

export function buildSystemSettingPayload(setting: Pick<SystemSetting, 'key' | 'value' | 'description' | 'isSecret'>) {
  return {
    key: setting.key,
    value: setting.value,
    description: setting.description ?? undefined,
    isSecret: setting.isSecret
  };
}

export async function apiFetch<T>(path: string, options: RequestInit = {}, token?: string | null, allowRetry = true): Promise<T> {
  const resolvedToken = token ?? (typeof window !== 'undefined' ? window.localStorage.getItem(AUTH_TOKEN_KEY) : null);

  let response: Response;
  try {
    response = await fetch(`${getApiUrl()}${path}`, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...(resolvedToken ? { Authorization: `Bearer ${resolvedToken}` } : {}),
        ...options.headers
      }
    });
  } catch (error) {
    throw mapFetchError(error);
  }

  if (!response.ok) {
    const error = await parseErrorResponse(response);

    if (error.code === 'PIN_REQUIRED') {
      notifyPinRequired(error.sessionId ?? window.localStorage.getItem(AUTH_SESSION_KEY) ?? '');
      throw error;
    }

    if (allowRetry && resolvedToken && error.code === 'TOKEN_EXPIRED') {
      const refreshed = await trySilentTokenRefresh();
      if (refreshed?.accessToken) {
        return apiFetch<T>(path, options, refreshed.accessToken, false);
      }
      if (refreshed?.requiresPin) {
        throw error;
      }
    }

    throw error;
  }

  return response.json() as Promise<T>;
}

export function getDeviceFingerprint(): string {
  if (typeof window === 'undefined') return 'server';
  const key = 'lendry_device_fingerprint';
  const existing = window.localStorage.getItem(key);
  if (existing) return existing;
  const value = crypto.randomUUID();
  window.localStorage.setItem(key, value);
  return value;
}

export interface MediaAccessResponse {
  accessUrl: string;
  expiresAt: string;
}

export async function fetchDocumentPhotoUrl(userId: string, storageKey: string, token?: string | null) {
  const query = new URLSearchParams({ storageKey });
  return apiFetch<MediaAccessResponse>(`/media/users/${userId}/documents/photo-url?${query.toString()}`, {}, token);
}

export interface AccountDeletionStatus {
  pending: boolean;
  deletionRequestedAt?: string;
  effectiveAt?: string;
  graceDays: number;
}

export async function requestAccountDeletion(userId: string, token?: string | null) {
  return apiFetch<AccountDeletionStatus & { userId: string }>(
    `/profile/users/${userId}/self-delete`,
    { method: 'POST' },
    token
  );
}

export async function cancelAccountDeletion(userId: string, token?: string | null) {
  return apiFetch<{ userId: string; cancelled: boolean }>(
    `/profile/users/${userId}/self-delete/cancel`,
    { method: 'POST' },
    token
  );
}

export async function fetchAccountDeletionStatus(userId: string, token?: string | null) {
  return apiFetch<AccountDeletionStatus>(`/profile/users/${userId}/self-delete/status`, {}, token);
}

/** @deprecated Используйте requestAccountDeletion — удаление теперь отложенное */
export async function softDeleteProfile(userId: string, token?: string | null) {
  return requestAccountDeletion(userId, token);
}

export interface UserAddress {
  id: string;
  userId: string;
  label: 'HOME' | 'WORK' | 'OTHER';
  city: string;
  street: string;
  house: string;
  apartment?: string;
  comment?: string;
  latitude?: number;
  longitude?: number;
  fullAddress?: string;
  createdAt: string;
  updatedAt: string;
}

export async function fetchUserAddresses(userId: string, token?: string | null) {
  return apiFetch<{ addresses?: UserAddress[] }>(`/addresses/users/${userId}`, {}, token);
}

export async function upsertUserAddress(
  userId: string,
  body: {
    addressId?: string;
    label: UserAddress['label'];
    city: string;
    street: string;
    house: string;
    apartment?: string;
    comment?: string;
    latitude?: number;
    longitude?: number;
    fullAddress?: string;
  },
  token?: string | null
) {
  return apiFetch<UserAddress>(`/addresses/users/${userId}`, { method: 'POST', body: JSON.stringify(body) }, token);
}

export async function deleteUserAddress(userId: string, addressId: string, token?: string | null) {
  return apiFetch<{ count: number }>(`/addresses/users/${userId}/${addressId}`, { method: 'DELETE' }, token);
}

export interface FamilyMember {
  id: string;
  userId: string;
  role: string;
  displayName: string;
  hasAvatar: boolean;
  isVerified?: boolean;
  verificationIcon?: string | null;
  isBot?: boolean;
  botUsername?: string;
}

export interface FamilyGroup {
  id: string;
  ownerId: string;
  name: string;
  hasAvatar: boolean;
  members?: FamilyMember[];
  botFatherAvailable?: boolean;
  getMyIdBotAvailable?: boolean;
}

export interface BotChatMessage {
  id: string;
  direction: 'in' | 'out';
  text: string;
  messageType: string;
  messageId: number;
  createdAt: string;
  replyMarkupJson?: string | null;
  isPinned?: boolean;
  pinnedAt?: string | null;
}

export interface BotChatMeta {
  botId?: string;
  botOwnerId?: string;
  botUsername?: string;
  botDisplayName?: string;
  composerWebAppUrl?: string | null;
  composerMenuButtonJson?: string | null;
  manageWebAppUrl?: string;
}

export interface FamilyInvite {
  id: string;
  groupId: string;
  groupName: string;
  inviterId: string;
  inviterName: string;
  inviteeUserId?: string;
  targetEmail?: string;
  targetPhone?: string;
  status: string;
  expiresAt: string;
}

export interface AppNotification {
  id: string;
  type: string;
  title: string;
  message: string;
  payloadJson?: string;
  isRead: boolean;
  createdAt: string;
}

export interface ChatPollOption {
  id: string;
  text: string;
  voteCount: number;
  votedByMe: boolean;
}

export interface ChatMessageReaction {
  emoji: string;
  count: number;
  reactedByMe: boolean;
}

export interface ChatMessage {
  id: string;
  roomId: string;
  senderId: string;
  senderName: string;
  senderHasAvatar: boolean;
  senderIsVerified?: boolean;
  senderVerificationIcon?: string | null;
  type: string;
  content?: string;
  isEncrypted?: boolean;
  replyToId?: string;
  storageKey?: string;
  mimeType?: string;
  metadataJson?: string;
  createdAt: string;
  editedAt?: string;
  isDeleted?: boolean;
  isPinned?: boolean;
  pinnedAt?: string;
  pinnedById?: string;
  readByAll?: boolean;
  poll?: {
    id: string;
    question: string;
    allowsMultiple: boolean;
    isAnonymous: boolean;
    options: ChatPollOption[];
  };
  reactions?: ChatMessageReaction[];
}

export interface FamilyPresenceMember {
  userId: string;
  displayName: string;
  online: boolean;
  lastSeenAt?: string;
}

export interface FamilyPresence {
  members: FamilyPresenceMember[];
  onlineCount: number;
}

export interface ChatRoomMember {
  id: string;
  userId: string;
  displayName: string;
  hasAvatar: boolean;
  isVerified?: boolean;
  verificationIcon?: string | null;
  notificationsMuted: boolean;
  isPinned?: boolean;
  pinnedAt?: string;
  familyRole?: string;
  isChatCreator?: boolean;
}

export interface ChatRoom {
  id: string;
  groupId: string;
  type: string;
  name: string;
  peerUserId?: string;
  botUsername?: string;
  isE2E?: boolean;
  hasAvatar: boolean;
  createdById?: string;
  updatedAt: string;
  isPinned?: boolean;
  pinnedAt?: string;
  members: ChatRoomMember[];
  lastMessage?: ChatMessage;
}

export async function fetchFamilyGroups(userId: string, token?: string | null) {
  return apiFetch<{ groups?: FamilyGroup[] }>(`/family/users/${userId}/groups`, {}, token);
}

export async function fetchFamilyGroup(groupId: string, token?: string | null) {
  return apiFetch<FamilyGroup>(`/family/groups/${groupId}`, {}, token);
}

export async function updateFamilyGroup(groupId: string, name: string, token?: string | null) {
  return apiFetch<FamilyGroup>(`/family/groups/${groupId}`, { method: 'PATCH', body: JSON.stringify({ name }) }, token);
}

export async function deleteFamilyGroup(groupId: string, token?: string | null) {
  return apiFetch<{ count: number }>(`/family/groups/${groupId}`, { method: 'DELETE' }, token);
}

export async function sendFamilyInvite(groupId: string, payload: { inviteeUserId: string; target?: string }, token?: string | null) {
  return apiFetch<FamilyInvite>(`/family/groups/${groupId}/invites`, { method: 'POST', body: JSON.stringify(payload) }, token);
}

export async function fetchBotChatMessages(botRef: string, token?: string | null, roomId?: string) {
  const suffix = roomId ? `?roomId=${encodeURIComponent(roomId)}` : '';
  return apiFetch<{
    messages?: BotChatMessage[];
    botUsername?: string;
    botDisplayName?: string;
    botId?: string;
    botOwnerId?: string;
    composerWebAppUrl?: string;
    composerMenuButtonJson?: string;
    manageWebAppUrl?: string;
  }>(`/bots/by-username/${encodeURIComponent(botRef)}/messages${suffix}`, {}, token);
}

export async function sendBotMessage(botRef: string, text: string, token?: string | null, roomId?: string) {
  return apiFetch<{ updateId?: number; messageId?: number; chatId?: string }>(
    `/bots/by-username/${encodeURIComponent(botRef)}/messages`,
    { method: 'POST', body: JSON.stringify({ text, roomId }) },
    token
  );
}

export async function submitBotCallback(botRef: string, messageId: number, callbackData: string, token?: string | null) {
  return apiFetch<{ updateId?: number; callbackQueryId?: string; messageId?: number }>(
    `/bots/by-username/${encodeURIComponent(botRef)}/callback`,
    { method: 'POST', body: JSON.stringify({ messageId, callbackData }) },
    token
  );
}

export async function fetchFamilyInvites(token?: string | null) {
  return apiFetch<{ invites?: FamilyInvite[] }>('/family/invites', {}, token);
}

export async function respondFamilyInvite(inviteId: string, accept: boolean, token?: string | null) {
  return apiFetch<FamilyInvite>(`/family/invites/${inviteId}/respond`, { method: 'POST', body: JSON.stringify({ accept }) }, token);
}

export async function fetchNotifications(token?: string | null, unreadOnly = false) {
  return apiFetch<{ notifications?: AppNotification[] }>(`/notifications?unreadOnly=${unreadOnly}`, {}, token);
}

export async function fetchUnreadNotificationCount(token?: string | null) {
  return apiFetch<{ count: number }>('/notifications/unread-count', {}, token);
}

export async function markNotificationRead(notificationId: string, token?: string | null) {
  return apiFetch(`/notifications/${notificationId}/read`, { method: 'PATCH', body: '{}' }, token);
}

export async function markAllNotificationsRead(token?: string | null) {
  return apiFetch('/notifications/read-all', { method: 'POST' }, token);
}

export async function fetchChatRooms(groupId: string, token?: string | null) {
  return apiFetch<{ rooms?: ChatRoom[] }>(`/chat/groups/${groupId}/rooms`, {}, token);
}

export async function createChatRoom(groupId: string, name: string, memberUserIds: string[], token?: string | null) {
  return apiFetch<ChatRoom>(`/chat/groups/${groupId}/rooms`, {
    method: 'POST',
    body: JSON.stringify({ name, memberUserIds })
  }, token);
}

export async function addChatRoomMember(roomId: string, memberUserId: string, token?: string | null) {
  return apiFetch<ChatRoom>(`/chat/rooms/${roomId}/members`, {
    method: 'POST',
    body: JSON.stringify({ memberUserId })
  }, token);
}

export async function removeChatRoomMember(roomId: string, memberUserId: string, token?: string | null) {
  return apiFetch<ChatRoom>(`/chat/rooms/${roomId}/members/${memberUserId}`, { method: 'DELETE' }, token);
}

export async function removeFamilyMember(memberId: string, token?: string | null) {
  return apiFetch<{ count: number }>(`/family/members/${memberId}`, { method: 'DELETE' }, token);
}

export async function fetchChatMessages(roomId: string, token?: string | null, beforeMessageId?: string) {
  const query = beforeMessageId ? `?beforeMessageId=${beforeMessageId}` : '';
  return apiFetch<{ messages?: ChatMessage[] }>(`/chat/rooms/${roomId}/messages${query}`, {}, token);
}

export async function sendChatMessage(
  roomId: string,
  body: {
    type: string;
    content?: string;
    isEncrypted?: boolean;
    replyToId?: string;
    storageKey?: string;
    mimeType?: string;
    metadataJson?: string;
    poll?: { question: string; options: string[]; allowsMultiple?: boolean; isAnonymous?: boolean };
  },
  token?: string | null
) {
  return apiFetch<ChatMessage>(`/chat/rooms/${roomId}/messages`, { method: 'POST', body: JSON.stringify(body) }, token);
}

export async function fetchUserE2EPublicKey(userId: string, token?: string | null) {
  return apiFetch<{ userId?: string; e2ePublicKey?: string }>(`/profile/users/${userId}/e2e-public-key`, {}, token);
}

export async function setUserE2EPublicKey(userId: string, publicKey: string, token?: string | null) {
  return apiFetch<{ userId?: string; e2ePublicKey?: string }>(
    `/profile/users/${userId}/e2e-public-key`,
    { method: 'PATCH', body: JSON.stringify({ publicKey }) },
    token
  );
}

export interface ManagedBot {
  id: string;
  name: string;
  username: string;
  tokenPrefix?: string;
  description?: string | null;
  aboutText?: string | null;
  botPicUrl?: string | null;
  menuButtonJson?: string | null;
  webAppUrl?: string | null;
  isActive?: boolean;
  isSystemBot?: boolean;
  ownerId?: string;
  createdAt?: string;
  updatedAt?: string;
  owner?: { id: string; displayName: string; username?: string };
  messageCount?: number;
  chatCount?: number;
}

export interface AdminBotMetrics {
  totalBots?: number;
  activeBots?: number;
  blockedBots?: number;
  totalMessages?: number;
  totalChats?: number;
}

export async function fetchAdminBots(
  params: { search?: string; page?: number; limit?: number },
  token?: string | null
) {
  const query = new URLSearchParams();
  if (params.search?.trim()) query.set('search', params.search.trim());
  if (params.page) query.set('page', String(params.page));
  if (params.limit) query.set('limit', String(params.limit));
  const suffix = query.toString() ? `?${query.toString()}` : '';
  return apiFetch<{ bots?: ManagedBot[]; total?: number }>(`/admin/bots${suffix}`, {}, token);
}

export async function fetchAdminBotMetrics(token?: string | null) {
  return apiFetch<AdminBotMetrics>('/admin/bots/metrics', {}, token);
}

export async function fetchAdminBotAccounts(search: string | undefined, token?: string | null) {
  const suffix = search?.trim() ? `?search=${encodeURIComponent(search.trim())}` : '';
  return apiFetch<{ users: AdminUser[] }>(`/admin/users/bot-accounts${suffix}`, {}, token);
}

export async function fetchUserTotpStatus(userId: string, token?: string | null) {
  return apiFetch<TotpStatusResponse>(`/security/users/${userId}/totp/status`, {}, token);
}

export async function adminDisableUserTotp(userId: string, token?: string | null) {
  return apiFetch<TotpStatusResponse>(`/admin/users/${userId}/totp/admin-disable`, { method: 'POST' }, token);
}

export async function setAdminBotActive(botId: string, isActive: boolean, token?: string | null) {
  return apiFetch<ManagedBot>(`/admin/bots/${botId}/active`, {
    method: 'PATCH',
    body: JSON.stringify({ isActive })
  }, token);
}

export async function createE2ERoom(groupId: string, peerUserId: string, token?: string | null) {
  return apiFetch<ChatRoom>(`/chat/groups/${groupId}/e2e-rooms`, {
    method: 'POST',
    body: JSON.stringify({ peerUserId })
  }, token);
}

export async function fetchMyBots(token?: string | null) {
  return apiFetch<{ bots?: ManagedBot[]; total?: number }>('/bots', {}, token);
}

export async function createManagedBot(body: { name: string; username: string }, token?: string | null) {
  return apiFetch<{ bot?: ManagedBot; token?: string }>('/bots', { method: 'POST', body: JSON.stringify(body) }, token);
}

export async function fetchBot(botId: string, token?: string | null) {
  return apiFetch<ManagedBot>(`/bots/${botId}`, {}, token);
}

export async function updateManagedBot(botId: string, body: { name?: string; username?: string }, token?: string | null) {
  return apiFetch<ManagedBot>(`/bots/${botId}`, { method: 'PATCH', body: JSON.stringify(body) }, token);
}

export async function updateManagedBotProfile(
  botId: string,
  body: {
    description?: string;
    aboutText?: string;
    botPicUrl?: string;
    menuButtonUrl?: string;
    menuButtonText?: string;
    menuButtonJson?: string;
  },
  token?: string | null
) {
  return apiFetch<ManagedBot>(`/bots/${botId}/profile`, { method: 'PATCH', body: JSON.stringify(body) }, token);
}

export async function revokeManagedBotToken(botId: string, token?: string | null) {
  return apiFetch<{ bot?: ManagedBot; token?: string }>(`/bots/${botId}/revoke-token`, { method: 'POST' }, token);
}

export async function voteChatPoll(messageId: string, optionIds: string[], token?: string | null) {
  return apiFetch<ChatMessage>(`/chat/messages/${messageId}/vote`, { method: 'POST', body: JSON.stringify({ optionIds }) }, token);
}

export async function setChatRoomMuted(roomId: string, muted: boolean, token?: string | null) {
  return apiFetch(`/chat/rooms/${roomId}/mute`, { method: 'POST', body: JSON.stringify({ muted }) }, token);
}

export async function setChatRoomPinned(roomId: string, pinned: boolean, token?: string | null) {
  return apiFetch<ChatRoom>(`/chat/rooms/${roomId}`, {
    method: 'PATCH',
    body: JSON.stringify({ pinned })
  }, token);
}

export async function editChatMessage(messageId: string, content: string, token?: string | null) {
  return apiFetch<ChatMessage>(`/chat/messages/${messageId}`, { method: 'PATCH', body: JSON.stringify({ content }) }, token);
}

export async function deleteChatMessage(messageId: string, token?: string | null) {
  return apiFetch<ChatMessage>(`/chat/messages/${messageId}`, { method: 'DELETE' }, token);
}

export async function toggleChatMessageReaction(messageId: string, emoji: string, token?: string | null) {
  return apiFetch<ChatMessage>(`/chat/messages/${messageId}/reactions`, {
    method: 'POST',
    body: JSON.stringify({ emoji })
  }, token);
}

export async function setChatMessagePinned(messageId: string, pinned: boolean, token?: string | null) {
  return apiFetch<ChatMessage>(`/chat/messages/${messageId}/pin`, {
    method: 'POST',
    body: JSON.stringify({ pinned })
  }, token);
}

export async function forwardChatMessages(targetRoomId: string, messageIds: string[], token?: string | null) {
  return apiFetch<{ messages?: ChatMessage[] }>(`/chat/rooms/${targetRoomId}/forward`, {
    method: 'POST',
    body: JSON.stringify({ messageIds })
  }, token);
}

export async function deleteChatRoom(roomId: string, token?: string | null) {
  return apiFetch<{ count?: number }>(`/chat/rooms/${roomId}`, { method: 'DELETE' }, token);
}

export async function markChatRoomRead(roomId: string, lastMessageId: string | undefined, token?: string | null) {
  return apiFetch<{ count: number }>(`/chat/rooms/${roomId}/read`, {
    method: 'POST',
    body: JSON.stringify({ lastMessageId })
  }, token);
}

export async function reportChatTyping(roomId: string, token?: string | null) {
  return apiFetch<{ success?: boolean }>(`/chat/rooms/${roomId}/typing`, { method: 'POST' }, token);
}

export async function fetchFamilyPresence(groupId: string, token?: string | null) {
  return apiFetch<FamilyPresence>(`/family/groups/${groupId}/presence`, {}, token);
}

/** @deprecated Используйте getWsUrl() — URL может переключаться на same-origin /idp-api/ws в браузере */
export const WS_URL = configuredWsUrl;
