import { gql, GraphQLClient } from "graphql-request";
import { apiUrl, getGraphQLClient } from "$lib/api";
import type {
  Analytics,
  Article,
  Comment,
  CurrentUser,
  Model,
  Recording,
  Stats,
  User,
  Video,
  VideoLikeStatus,
  VideoLikeResponse,
  VideoPlayResponse,
} from "$lib/types";
import { logger } from "$lib/logger";

export class UnauthorizedError extends Error {
  constructor() {
    super("Unauthorized");
    this.name = "UnauthorizedError";
  }
}

function isUnauthorizedError(error: unknown): boolean {
  if (error && typeof error === "object" && "response" in error) {
    const resp = (error as { response?: { errors?: { message: string }[] } }).response;
    if (resp?.errors?.some((e) => e.message === "Unauthorized")) return true;
  }
  const msg = error instanceof Error ? error.message : String(error);
  return msg.startsWith("Unauthorized");
}

// Helper to log API calls
async function loggedApiCall<T>(
  operationName: string,
  operation: () => Promise<T>,
  context?: Record<string, unknown>,
): Promise<T> {
  const startTime = Date.now();

  try {
    logger.debug(`🔄 API: ${operationName}`, { context });
    const result = await operation();
    const duration = Date.now() - startTime;
    logger.info(`✅ API: ${operationName} succeeded`, { duration, context });
    return result;
  } catch (error) {
    const duration = Date.now() - startTime;
    if (isUnauthorizedError(error)) {
      logger.debug(`🔒 API: ${operationName} unauthorized`, { duration, context });
      throw new UnauthorizedError();
    }
    logger.error(`❌ API: ${operationName} failed`, {
      duration,
      context,
      error: error instanceof Error ? error : new Error(String(error)),
    });
    throw error;
  }
}

// For server-side auth checks: forward cookie header manually
function getAuthClient(token: string, fetchFn?: typeof globalThis.fetch) {
  return new GraphQLClient(`${apiUrl}/graphql`, {
    fetch: fetchFn || globalThis.fetch,
    headers: { cookie: `session_token=${token}` },
  });
}

// ─── Auth ────────────────────────────────────────────────────────────────────

const ME_QUERY = gql`
  query Me {
    me {
      id
      email
      first_name
      last_name
      artist_name
      slug
      description
      tags
      role
      is_admin
      avatar
      banner
      email_verified
      date_created
    }
  }
`;

export async function isAuthenticated(token: string, fetchFn?: typeof globalThis.fetch) {
  return loggedApiCall(
    "isAuthenticated",
    async () => {
      try {
        const client = getAuthClient(token, fetchFn);
        const data = await client.request<{ me: CurrentUser | null }>(ME_QUERY);
        if (data.me) {
          return { authenticated: true, user: data.me };
        }
        return { authenticated: false };
      } catch {
        return { authenticated: false };
      }
    },
    { hasToken: !!token },
  );
}

const LOGIN_MUTATION = gql`
  mutation Login($email: String!, $password: String!) {
    login(email: $email, password: $password) {
      id
      email
      first_name
      last_name
      artist_name
      slug
      description
      tags
      role
      avatar
      banner
      email_verified
      date_created
    }
  }
`;

export async function login(email: string, password: string) {
  return loggedApiCall(
    "login",
    async () => {
      const data = await getGraphQLClient().request<{ login: CurrentUser }>(LOGIN_MUTATION, {
        email,
        password,
      });
      return data.login;
    },
    { email },
  );
}

const LOGOUT_MUTATION = gql`
  mutation Logout {
    logout
  }
`;

export async function logout() {
  return loggedApiCall("logout", async () => {
    await getGraphQLClient().request(LOGOUT_MUTATION);
  });
}

const REGISTER_MUTATION = gql`
  mutation Register($email: String!, $password: String!, $firstName: String!, $lastName: String!) {
    register(email: $email, password: $password, firstName: $firstName, lastName: $lastName)
  }
`;

export async function register(
  email: string,
  password: string,
  firstName: string,
  lastName: string,
) {
  return loggedApiCall(
    "register",
    async () => {
      await getGraphQLClient().request(REGISTER_MUTATION, {
        email,
        password,
        firstName,
        lastName,
      });
    },
    { email, firstName, lastName },
  );
}

const VERIFY_EMAIL_MUTATION = gql`
  mutation VerifyEmail($token: String!) {
    verifyEmail(token: $token)
  }
`;

export async function verify(token: string, fetchFn?: typeof globalThis.fetch) {
  return loggedApiCall(
    "verify",
    async () => {
      await getGraphQLClient(fetchFn).request(VERIFY_EMAIL_MUTATION, { token });
    },
    { hasToken: !!token },
  );
}

const REQUEST_PASSWORD_MUTATION = gql`
  mutation RequestPasswordReset($email: String!) {
    requestPasswordReset(email: $email)
  }
`;

export async function requestPassword(email: string) {
  return loggedApiCall(
    "requestPassword",
    async () => {
      await getGraphQLClient().request(REQUEST_PASSWORD_MUTATION, { email });
    },
    { email },
  );
}

const RESET_PASSWORD_MUTATION = gql`
  mutation ResetPassword($token: String!, $newPassword: String!) {
    resetPassword(token: $token, newPassword: $newPassword)
  }
`;

export async function resetPassword(token: string, password: string) {
  return loggedApiCall(
    "resetPassword",
    async () => {
      await getGraphQLClient().request(RESET_PASSWORD_MUTATION, {
        token,
        newPassword: password,
      });
    },
    { hasToken: !!token },
  );
}

// ─── Articles ────────────────────────────────────────────────────────────────

const ARTICLES_QUERY = gql`
  query GetArticles(
    $search: String
    $category: String
    $sortBy: String
    $offset: Int
    $limit: Int
    $featured: Boolean
    $tag: String
  ) {
    articles(
      search: $search
      category: $category
      sortBy: $sortBy
      offset: $offset
      limit: $limit
      featured: $featured
      tag: $tag
    ) {
      items {
        id
        slug
        title
        excerpt
        content
        image
        tags
        publish_date
        category
        featured
        author {
          id
          artist_name
          slug
          avatar
        }
      }
      total
    }
  }
`;

export async function getArticles(
  params: {
    search?: string;
    category?: string;
    sortBy?: string;
    offset?: number;
    limit?: number;
    featured?: boolean;
    tag?: string;
  } = {},
  fetchFn?: typeof globalThis.fetch,
): Promise<{ items: Article[]; total: number }> {
  return loggedApiCall("getArticles", async () => {
    const data = await getGraphQLClient(fetchFn).request<{
      articles: { items: Article[]; total: number };
    }>(ARTICLES_QUERY, params);
    return data.articles;
  });
}

const ARTICLE_CATEGORIES_QUERY = gql`
  query GetArticleCategories {
    articleCategories
  }
`;

export async function getArticleCategories(
  fetchFn?: typeof globalThis.fetch,
): Promise<string[]> {
  return loggedApiCall("getArticleCategories", async () => {
    const data = await getGraphQLClient(fetchFn).request<{ articleCategories: string[] }>(
      ARTICLE_CATEGORIES_QUERY,
    );
    return data.articleCategories;
  });
}

const ARTICLE_BY_SLUG_QUERY = gql`
  query GetArticleBySlug($slug: String!) {
    article(slug: $slug) {
      id
      slug
      title
      excerpt
      content
      image
      tags
      publish_date
      category
      featured
      author {
        id
        artist_name
        slug
        avatar
        description
      }
    }
  }
`;

export async function getArticleBySlug(slug: string, fetchFn?: typeof globalThis.fetch) {
  return loggedApiCall(
    "getArticleBySlug",
    async () => {
      const data = await getGraphQLClient(fetchFn).request<{ article: Article | null }>(
        ARTICLE_BY_SLUG_QUERY,
        { slug },
      );
      if (!data.article) throw new Error("Article not found");
      return data.article;
    },
    { slug },
  );
}

// ─── Videos ──────────────────────────────────────────────────────────────────

const VIDEOS_QUERY = gql`
  query GetVideos(
    $modelId: String
    $featured: Boolean
    $limit: Int
    $search: String
    $offset: Int
    $sortBy: String
    $duration: String
    $tag: String
  ) {
    videos(
      modelId: $modelId
      featured: $featured
      limit: $limit
      search: $search
      offset: $offset
      sortBy: $sortBy
      duration: $duration
      tag: $tag
    ) {
      items {
        id
        slug
        title
        description
        image
        movie
        tags
        upload_date
        premium
        featured
        likes_count
        plays_count
        models {
          id
          artist_name
          slug
          avatar
        }
        movie_file {
          id
          filename
          mime_type
          duration
        }
      }
      total
    }
  }
`;

export async function getVideos(
  params: {
    search?: string;
    sortBy?: string;
    duration?: string;
    offset?: number;
    limit?: number;
    tag?: string;
  } = {},
  fetchFn?: typeof globalThis.fetch,
): Promise<{ items: Video[]; total: number }> {
  return loggedApiCall("getVideos", async () => {
    const data = await getGraphQLClient(fetchFn).request<{
      videos: { items: Video[]; total: number };
    }>(VIDEOS_QUERY, params);
    return data.videos;
  });
}

export async function getVideosForModel(id: string, fetchFn?: typeof globalThis.fetch) {
  return loggedApiCall(
    "getVideosForModel",
    async () => {
      const data = await getGraphQLClient(fetchFn).request<{
        videos: { items: Video[]; total: number };
      }>(VIDEOS_QUERY, { modelId: id, limit: 10000 });
      return data.videos.items;
    },
    { modelId: id },
  );
}

export async function getFeaturedVideos(
  limit: number,
  fetchFn: typeof globalThis.fetch = globalThis.fetch,
) {
  return loggedApiCall(
    "getFeaturedVideos",
    async () => {
      const data = await getGraphQLClient(fetchFn).request<{
        videos: { items: Video[]; total: number };
      }>(VIDEOS_QUERY, { featured: true, limit });
      return data.videos.items;
    },
    { limit },
  );
}

const VIDEO_BY_SLUG_QUERY = gql`
  query GetVideoBySlug($slug: String!) {
    video(slug: $slug) {
      id
      slug
      title
      description
      image
      movie
      tags
      upload_date
      premium
      featured
      likes_count
      plays_count
      models {
        id
        artist_name
        slug
        avatar
      }
      movie_file {
        id
        filename
        mime_type
        duration
      }
    }
  }
`;

export async function getVideoBySlug(slug: string, fetchFn?: typeof globalThis.fetch) {
  return loggedApiCall(
    "getVideoBySlug",
    async () => {
      const data = await getGraphQLClient(fetchFn).request<{ video: Video | null }>(
        VIDEO_BY_SLUG_QUERY,
        { slug },
      );
      if (!data.video) throw new Error("Video not found");
      return data.video;
    },
    { slug },
  );
}

// ─── Models ──────────────────────────────────────────────────────────────────

const MODELS_QUERY = gql`
  query GetModels(
    $featured: Boolean
    $limit: Int
    $search: String
    $offset: Int
    $sortBy: String
    $tag: String
  ) {
    models(
      featured: $featured
      limit: $limit
      search: $search
      offset: $offset
      sortBy: $sortBy
      tag: $tag
    ) {
      items {
        id
        slug
        artist_name
        description
        avatar
        banner
        photo
        tags
        date_created
        photos {
          id
          filename
        }
      }
      total
    }
  }
`;

export async function getModels(
  params: { search?: string; sortBy?: string; offset?: number; limit?: number; tag?: string } = {},
  fetchFn?: typeof globalThis.fetch,
): Promise<{ items: Model[]; total: number }> {
  return loggedApiCall("getModels", async () => {
    const data = await getGraphQLClient(fetchFn).request<{
      models: { items: Model[]; total: number };
    }>(MODELS_QUERY, params);
    return data.models;
  });
}

export async function getFeaturedModels(
  limit = 3,
  fetchFn: typeof globalThis.fetch = globalThis.fetch,
) {
  return loggedApiCall(
    "getFeaturedModels",
    async () => {
      const data = await getGraphQLClient(fetchFn).request<{
        models: { items: Model[]; total: number };
      }>(MODELS_QUERY, { featured: true, limit });
      return data.models.items;
    },
    { limit },
  );
}

const MODEL_BY_SLUG_QUERY = gql`
  query GetModelBySlug($slug: String!) {
    model(slug: $slug) {
      id
      slug
      artist_name
      description
      avatar
      banner
      photo
      tags
      date_created
      photos {
        id
        filename
      }
    }
  }
`;

export async function getModelBySlug(slug: string, fetchFn?: typeof globalThis.fetch) {
  return loggedApiCall(
    "getModelBySlug",
    async () => {
      const data = await getGraphQLClient(fetchFn).request<{ model: Model | null }>(
        MODEL_BY_SLUG_QUERY,
        { slug },
      );
      if (!data.model) throw new Error("Model not found");
      return data.model;
    },
    { slug },
  );
}

// ─── Profile ─────────────────────────────────────────────────────────────────

const UPDATE_PROFILE_MUTATION = gql`
  mutation UpdateProfile(
    $firstName: String
    $lastName: String
    $artistName: String
    $description: String
    $tags: [String!]
    $avatar: String
  ) {
    updateProfile(
      firstName: $firstName
      lastName: $lastName
      artistName: $artistName
      description: $description
      tags: $tags
      avatar: $avatar
    ) {
      id
      email
      first_name
      last_name
      artist_name
      slug
      description
      tags
      role
      avatar
      banner
      date_created
    }
  }
`;

export async function updateProfile(user: Partial<User> & { password?: string }) {
  return loggedApiCall(
    "updateProfile",
    async () => {
      const data = await getGraphQLClient().request<{ updateProfile: User }>(
        UPDATE_PROFILE_MUTATION,
        {
          firstName: user.first_name,
          lastName: user.last_name,
          artistName: user.artist_name,
          description: user.description,
          tags: user.tags,
          avatar: user.avatar,
        },
      );
      return data.updateProfile;
    },
    { userId: user.id },
  );
}

// ─── Stats ───────────────────────────────────────────────────────────────────

const STATS_QUERY = gql`
  query GetStats {
    stats {
      videos_count
      models_count
      viewers_count
    }
  }
`;

export async function getStats(fetchFn?: typeof globalThis.fetch) {
  return loggedApiCall("getStats", async () => {
    const data = await getGraphQLClient(fetchFn).request<{ stats: Stats }>(STATS_QUERY);
    return data.stats;
  });
}

// Stub — Directus folder concept dropped
export async function getFolders(_fetchFn?: typeof globalThis.fetch) {
  return loggedApiCall("getFolders", async () => [] as { id: string; name: string }[]);
}

// ─── Files ───────────────────────────────────────────────────────────────────

export async function removeFile(id: string) {
  return loggedApiCall(
    "removeFile",
    async () => {
      // File deletion via REST DELETE /assets/:id (backend handles it)
      const response = await fetch(`${apiUrl}/assets/${id}`, {
        method: "DELETE",
        credentials: "include",
      });
      if (!response.ok && response.status !== 404)
        throw new Error(`Failed to delete file: ${response.statusText}`);
    },
    { fileId: id },
  );
}

export async function uploadFile(data: FormData) {
  return loggedApiCall("uploadFile", async () => {
    const response = await fetch(`${apiUrl}/upload`, {
      method: "POST",
      body: data,
      credentials: "include",
    });
    if (!response.ok) throw new Error(`Upload failed: ${response.statusText}`);
    return response.json();
  });
}

// ─── Comments ────────────────────────────────────────────────────────────────

const COMMENTS_FOR_VIDEO_QUERY = gql`
  query CommentsForVideo($videoId: String!) {
    commentsForVideo(videoId: $videoId) {
      id
      comment
      item_id
      user_id
      date_created
      user {
        id
        first_name
        last_name
        avatar
      }
    }
  }
`;

export async function getCommentsForVideo(item: string, fetchFn?: typeof globalThis.fetch) {
  return loggedApiCall(
    "getCommentsForVideo",
    async () => {
      const data = await getGraphQLClient(fetchFn).request<{
        commentsForVideo: {
          id: number;
          comment: string;
          item_id: string;
          user_id: string;
          date_created: string;
          user: {
            id: string;
            first_name: string | null;
            last_name: string | null;
            artist_name: string | null;
            avatar: string | null;
          } | null;
        }[];
      }>(COMMENTS_FOR_VIDEO_QUERY, { videoId: item });
      return data.commentsForVideo;
    },
    { videoId: item },
  );
}

const CREATE_COMMENT_MUTATION = gql`
  mutation CreateCommentForVideo($videoId: String!, $comment: String!) {
    createCommentForVideo(videoId: $videoId, comment: $comment) {
      id
      comment
      item_id
      user_id
      date_created
    }
  }
`;

export async function createCommentForVideo(item: string, comment: string) {
  return loggedApiCall(
    "createCommentForVideo",
    async () => {
      const data = await getGraphQLClient().request(CREATE_COMMENT_MUTATION, {
        videoId: item,
        comment,
      });
      return data;
    },
    { videoId: item, commentLength: comment.length },
  );
}

export async function countCommentsForModel(
  _user_created: string,
  _fetchFn?: typeof globalThis.fetch,
) {
  // Not directly available in new API, return 0
  return 0;
}

// ─── Tags ────────────────────────────────────────────────────────────────────

export async function getItemsByTag(
  category: "video" | "article" | "model",
  tag: string,
  fetchFn?: typeof globalThis.fetch,
) {
  return loggedApiCall(
    "getItemsByTag",
    async () => {
      switch (category) {
        case "video":
          return getVideos({ tag, limit: 1000 }, fetchFn).then((r) => r.items);
        case "model":
          return getModels({ tag, limit: 1000 }, fetchFn).then((r) => r.items);
        case "article":
          return getArticles({ tag, limit: 1000 }, fetchFn).then((r) => r.items);
      }
    },
    { category, tag },
  );
}

// ─── Recordings ──────────────────────────────────────────────────────────────

const RECORDINGS_QUERY = gql`
  query GetRecordings(
    $status: String
    $tags: String
    $linkedVideoId: String
    $limit: Int
    $page: Int
  ) {
    recordings(
      status: $status
      tags: $tags
      linkedVideoId: $linkedVideoId
      limit: $limit
      page: $page
    ) {
      id
      title
      description
      slug
      duration
      events
      device_info
      user_id
      status
      tags
      linked_video
      featured
      public
      date_created
      date_updated
    }
  }
`;

export async function getRecordings(fetchFn?: typeof globalThis.fetch, token?: string) {
  return loggedApiCall(
    "getRecordings",
    async () => {
      const client = token ? getAuthClient(token, fetchFn) : getGraphQLClient(fetchFn);
      const data = await client.request<{ recordings: Recording[] }>(RECORDINGS_QUERY);
      return data.recordings;
    },
    {},
  );
}

const CREATE_RECORDING_MUTATION = gql`
  mutation CreateRecording(
    $title: String!
    $description: String
    $duration: Int!
    $events: JSON!
    $deviceInfo: JSON!
    $tags: [String!]
    $status: String
    $linkedVideoId: String
  ) {
    createRecording(
      title: $title
      description: $description
      duration: $duration
      events: $events
      deviceInfo: $deviceInfo
      tags: $tags
      status: $status
      linkedVideoId: $linkedVideoId
    ) {
      id
      title
      description
      slug
      duration
      events
      device_info
      user_id
      status
      tags
      linked_video
      featured
      public
      date_created
      date_updated
    }
  }
`;

export async function createRecording(
  recording: {
    title: string;
    description?: string;
    duration: number;
    events: unknown[];
    device_info: unknown[];
    tags?: string[];
    status?: string;
  },
  fetchFn?: typeof globalThis.fetch,
) {
  return loggedApiCall(
    "createRecording",
    async () => {
      const data = await getGraphQLClient(fetchFn).request<{ createRecording: Recording }>(
        CREATE_RECORDING_MUTATION,
        {
          title: recording.title,
          description: recording.description,
          duration: recording.duration,
          events: recording.events,
          deviceInfo: recording.device_info,
          tags: recording.tags,
          status: recording.status,
        },
      );
      return data.createRecording;
    },
    { title: recording.title, eventCount: recording.events.length },
  );
}

const UPDATE_RECORDING_MUTATION = gql`
  mutation UpdateRecording($id: String!, $status: String, $public: Boolean) {
    updateRecording(id: $id, status: $status, public: $public) {
      id
      status
      public
    }
  }
`;

export async function updateRecording(id: string, fields: { status?: string; public?: boolean }) {
  return loggedApiCall("updateRecording", async () => {
    const data = await getGraphQLClient().request<{ updateRecording: Recording }>(
      UPDATE_RECORDING_MUTATION,
      { id, ...fields },
    );
    return data.updateRecording;
  });
}

const DELETE_RECORDING_MUTATION = gql`
  mutation DeleteRecording($id: String!) {
    deleteRecording(id: $id)
  }
`;

export async function deleteRecording(id: string) {
  return loggedApiCall(
    "deleteRecording",
    async () => {
      await getGraphQLClient().request(DELETE_RECORDING_MUTATION, { id });
    },
    { id },
  );
}

const RECORDING_QUERY = gql`
  query GetRecording($id: String!) {
    recording(id: $id) {
      id
      title
      description
      slug
      duration
      events
      device_info
      user_id
      status
      tags
      linked_video
      featured
      public
      date_created
      date_updated
    }
  }
`;

export async function getRecording(id: string, fetchFn?: typeof globalThis.fetch, token?: string) {
  return loggedApiCall(
    "getRecording",
    async () => {
      const client = token ? getAuthClient(token, fetchFn) : getGraphQLClient(fetchFn);
      const data = await client.request<{ recording: Recording | null }>(RECORDING_QUERY, { id });
      return data.recording;
    },
    { id },
  );
}

// ─── Video likes & plays ─────────────────────────────────────────────────────

const LIKE_VIDEO_MUTATION = gql`
  mutation LikeVideo($videoId: String!) {
    likeVideo(videoId: $videoId) {
      liked
      likes_count
    }
  }
`;

export async function likeVideo(videoId: string) {
  return loggedApiCall(
    "likeVideo",
    async () => {
      const data = await getGraphQLClient().request<{ likeVideo: VideoLikeResponse }>(
        LIKE_VIDEO_MUTATION,
        { videoId },
      );
      return data.likeVideo;
    },
    { videoId },
  );
}

const UNLIKE_VIDEO_MUTATION = gql`
  mutation UnlikeVideo($videoId: String!) {
    unlikeVideo(videoId: $videoId) {
      liked
      likes_count
    }
  }
`;

export async function unlikeVideo(videoId: string) {
  return loggedApiCall(
    "unlikeVideo",
    async () => {
      const data = await getGraphQLClient().request<{ unlikeVideo: VideoLikeResponse }>(
        UNLIKE_VIDEO_MUTATION,
        { videoId },
      );
      return data.unlikeVideo;
    },
    { videoId },
  );
}

const VIDEO_LIKE_STATUS_QUERY = gql`
  query VideoLikeStatus($videoId: String!) {
    videoLikeStatus(videoId: $videoId) {
      liked
    }
  }
`;

export async function getVideoLikeStatus(videoId: string, fetchFn?: typeof globalThis.fetch) {
  return loggedApiCall(
    "getVideoLikeStatus",
    async () => {
      const data = await getGraphQLClient(fetchFn).request<{ videoLikeStatus: VideoLikeStatus }>(
        VIDEO_LIKE_STATUS_QUERY,
        { videoId },
      );
      return data.videoLikeStatus;
    },
    { videoId },
  );
}

const RECORD_VIDEO_PLAY_MUTATION = gql`
  mutation RecordVideoPlay($videoId: String!, $sessionId: String) {
    recordVideoPlay(videoId: $videoId, sessionId: $sessionId) {
      success
      play_id
      plays_count
    }
  }
`;

export async function recordVideoPlay(videoId: string, sessionId?: string) {
  return loggedApiCall(
    "recordVideoPlay",
    async () => {
      const data = await getGraphQLClient().request<{ recordVideoPlay: VideoPlayResponse }>(
        RECORD_VIDEO_PLAY_MUTATION,
        { videoId, sessionId },
      );
      return data.recordVideoPlay;
    },
    { videoId },
  );
}

const UPDATE_VIDEO_PLAY_MUTATION = gql`
  mutation UpdateVideoPlay(
    $videoId: String!
    $playId: String!
    $durationWatched: Int!
    $completed: Boolean!
  ) {
    updateVideoPlay(
      videoId: $videoId
      playId: $playId
      durationWatched: $durationWatched
      completed: $completed
    )
  }
`;

export async function updateVideoPlay(
  videoId: string,
  playId: string,
  durationWatched: number,
  completed: boolean,
) {
  return loggedApiCall(
    "updateVideoPlay",
    async () => {
      await getGraphQLClient().request(UPDATE_VIDEO_PLAY_MUTATION, {
        videoId,
        playId,
        durationWatched,
        completed,
      });
    },
    { videoId, playId, durationWatched, completed },
  );
}

// ─── Delete comment ──────────────────────────────────────────────────────────

const DELETE_COMMENT_MUTATION = gql`
  mutation DeleteComment($id: Int!) {
    deleteComment(id: $id)
  }
`;

export async function deleteComment(id: number) {
  return loggedApiCall(
    "deleteComment",
    async () => {
      await getGraphQLClient().request(DELETE_COMMENT_MUTATION, { id });
    },
    { id },
  );
}

// ─── Admin: Users ─────────────────────────────────────────────────────────────

const ADMIN_LIST_USERS_QUERY = gql`
  query AdminListUsers($role: String, $search: String, $limit: Int, $offset: Int) {
    adminListUsers(role: $role, search: $search, limit: $limit, offset: $offset) {
      total
      items {
        id
        email
        first_name
        last_name
        artist_name
        slug
        role
        is_admin
        avatar
        email_verified
        date_created
      }
    }
  }
`;

export async function adminListUsers(
  opts: { role?: string; search?: string; limit?: number; offset?: number } = {},
  fetchFn?: typeof globalThis.fetch,
  token?: string,
) {
  return loggedApiCall(
    "adminListUsers",
    async () => {
      const client = token ? getAuthClient(token) : getGraphQLClient(fetchFn);
      const data = await client.request<{
        adminListUsers: { total: number; items: User[] };
      }>(ADMIN_LIST_USERS_QUERY, opts);
      return data.adminListUsers;
    },
    opts,
  );
}

const ADMIN_UPDATE_USER_MUTATION = gql`
  mutation AdminUpdateUser(
    $userId: String!
    $role: String
    $isAdmin: Boolean
    $firstName: String
    $lastName: String
    $artistName: String
    $avatarId: String
    $bannerId: String
    $photoId: String
  ) {
    adminUpdateUser(
      userId: $userId
      role: $role
      isAdmin: $isAdmin
      firstName: $firstName
      lastName: $lastName
      artistName: $artistName
      avatarId: $avatarId
      bannerId: $bannerId
      photoId: $photoId
    ) {
      id
      email
      first_name
      last_name
      artist_name
      role
      is_admin
      avatar
      banner
      photo
      date_created
    }
  }
`;

export async function adminUpdateUser(input: {
  userId: string;
  role?: string;
  isAdmin?: boolean;
  firstName?: string;
  lastName?: string;
  artistName?: string;
  avatarId?: string;
  bannerId?: string;
  photoId?: string;
}) {
  return loggedApiCall(
    "adminUpdateUser",
    async () => {
      const data = await getGraphQLClient().request<{ adminUpdateUser: User | null }>(
        ADMIN_UPDATE_USER_MUTATION,
        input,
      );
      return data.adminUpdateUser;
    },
    { userId: input.userId },
  );
}

const ADMIN_DELETE_USER_MUTATION = gql`
  mutation AdminDeleteUser($userId: String!) {
    adminDeleteUser(userId: $userId)
  }
`;

export async function adminDeleteUser(userId: string) {
  return loggedApiCall(
    "adminDeleteUser",
    async () => {
      await getGraphQLClient().request(ADMIN_DELETE_USER_MUTATION, { userId });
    },
    { userId },
  );
}

const ADMIN_GET_USER_QUERY = gql`
  query AdminGetUser($userId: String!) {
    adminGetUser(userId: $userId) {
      id
      email
      first_name
      last_name
      artist_name
      slug
      role
      is_admin
      avatar
      banner
      photo
      description
      tags
      email_verified
      date_created
      photos {
        id
        filename
      }
    }
  }
`;

export async function adminGetUser(userId: string, token?: string) {
  return loggedApiCall(
    "adminGetUser",
    async () => {
      const client = token ? getAuthClient(token) : getGraphQLClient();
      const data = await client.request<{
        adminGetUser: User & { photos: Array<{ id: string; filename: string }> };
      }>(ADMIN_GET_USER_QUERY, { userId });
      return data.adminGetUser;
    },
    { userId },
  );
}

const ADMIN_ADD_USER_PHOTO_MUTATION = gql`
  mutation AdminAddUserPhoto($userId: String!, $fileId: String!) {
    adminAddUserPhoto(userId: $userId, fileId: $fileId)
  }
`;

export async function adminAddUserPhoto(userId: string, fileId: string) {
  return loggedApiCall("adminAddUserPhoto", async () => {
    await getGraphQLClient().request(ADMIN_ADD_USER_PHOTO_MUTATION, { userId, fileId });
  });
}

const ADMIN_REMOVE_USER_PHOTO_MUTATION = gql`
  mutation AdminRemoveUserPhoto($userId: String!, $fileId: String!) {
    adminRemoveUserPhoto(userId: $userId, fileId: $fileId)
  }
`;

export async function adminRemoveUserPhoto(userId: string, fileId: string) {
  return loggedApiCall("adminRemoveUserPhoto", async () => {
    await getGraphQLClient().request(ADMIN_REMOVE_USER_PHOTO_MUTATION, { userId, fileId });
  });
}

// ─── Admin: Videos ────────────────────────────────────────────────────────────

const ADMIN_LIST_VIDEOS_QUERY = gql`
  query AdminListVideos(
    $search: String
    $premium: Boolean
    $featured: Boolean
    $limit: Int
    $offset: Int
  ) {
    adminListVideos(
      search: $search
      premium: $premium
      featured: $featured
      limit: $limit
      offset: $offset
    ) {
      items {
        id
        slug
        title
        description
        image
        movie
        tags
        upload_date
        premium
        featured
        likes_count
        plays_count
        models {
          id
          artist_name
          slug
          avatar
        }
        movie_file {
          id
          filename
          mime_type
          duration
        }
      }
      total
    }
  }
`;

export async function adminListVideos(
  opts: {
    search?: string;
    premium?: boolean;
    featured?: boolean;
    limit?: number;
    offset?: number;
  } = {},
  fetchFn?: typeof globalThis.fetch,
  token?: string,
): Promise<{ items: Video[]; total: number }> {
  return loggedApiCall("adminListVideos", async () => {
    const client = token ? getAuthClient(token) : getGraphQLClient(fetchFn);
    const data = await client.request<{ adminListVideos: { items: Video[]; total: number } }>(
      ADMIN_LIST_VIDEOS_QUERY,
      opts,
    );
    return data.adminListVideos;
  });
}

const ADMIN_GET_VIDEO_QUERY = gql`
  query AdminGetVideo($id: String!) {
    adminGetVideo(id: $id) {
      id
      slug
      title
      description
      image
      movie
      tags
      upload_date
      premium
      featured
      likes_count
      plays_count
      models {
        id
        artist_name
        slug
        avatar
      }
      movie_file {
        id
        filename
        mime_type
        duration
      }
    }
  }
`;

export async function adminGetVideo(
  id: string,
  fetchFn?: typeof globalThis.fetch,
  token?: string,
): Promise<Video | null> {
  return loggedApiCall("adminGetVideo", async () => {
    const client = token ? getAuthClient(token) : getGraphQLClient(fetchFn);
    const data = await client.request<{ adminGetVideo: Video | null }>(ADMIN_GET_VIDEO_QUERY, {
      id,
    });
    return data.adminGetVideo;
  });
}

const CREATE_VIDEO_MUTATION = gql`
  mutation CreateVideo(
    $title: String!
    $slug: String!
    $description: String
    $imageId: String
    $movieId: String
    $tags: [String!]
    $premium: Boolean
    $featured: Boolean
    $uploadDate: String
  ) {
    createVideo(
      title: $title
      slug: $slug
      description: $description
      imageId: $imageId
      movieId: $movieId
      tags: $tags
      premium: $premium
      featured: $featured
      uploadDate: $uploadDate
    ) {
      id
      slug
      title
    }
  }
`;

export async function createVideo(input: {
  title: string;
  slug: string;
  description?: string;
  imageId?: string;
  movieId?: string;
  tags?: string[];
  premium?: boolean;
  featured?: boolean;
  uploadDate?: string;
}) {
  return loggedApiCall(
    "createVideo",
    async () => {
      const data = await getGraphQLClient().request<{ createVideo: Video }>(
        CREATE_VIDEO_MUTATION,
        input,
      );
      return data.createVideo;
    },
    { title: input.title },
  );
}

const UPDATE_VIDEO_MUTATION = gql`
  mutation UpdateVideo(
    $id: String!
    $title: String
    $slug: String
    $description: String
    $imageId: String
    $movieId: String
    $tags: [String!]
    $premium: Boolean
    $featured: Boolean
    $uploadDate: String
  ) {
    updateVideo(
      id: $id
      title: $title
      slug: $slug
      description: $description
      imageId: $imageId
      movieId: $movieId
      tags: $tags
      premium: $premium
      featured: $featured
      uploadDate: $uploadDate
    ) {
      id
      slug
      title
    }
  }
`;

export async function updateVideo(input: {
  id: string;
  title?: string;
  slug?: string;
  description?: string;
  imageId?: string;
  movieId?: string;
  tags?: string[];
  premium?: boolean;
  featured?: boolean;
  uploadDate?: string;
}) {
  return loggedApiCall(
    "updateVideo",
    async () => {
      const data = await getGraphQLClient().request<{ updateVideo: Video | null }>(
        UPDATE_VIDEO_MUTATION,
        input,
      );
      return data.updateVideo;
    },
    { id: input.id },
  );
}

const DELETE_VIDEO_MUTATION = gql`
  mutation DeleteVideo($id: String!) {
    deleteVideo(id: $id)
  }
`;

export async function deleteVideo(id: string) {
  return loggedApiCall(
    "deleteVideo",
    async () => {
      await getGraphQLClient().request(DELETE_VIDEO_MUTATION, { id });
    },
    { id },
  );
}

const SET_VIDEO_MODELS_MUTATION = gql`
  mutation SetVideoModels($videoId: String!, $userIds: [String!]!) {
    setVideoModels(videoId: $videoId, userIds: $userIds)
  }
`;

export async function setVideoModels(videoId: string, userIds: string[]) {
  return loggedApiCall(
    "setVideoModels",
    async () => {
      await getGraphQLClient().request(SET_VIDEO_MODELS_MUTATION, { videoId, userIds });
    },
    { videoId, count: userIds.length },
  );
}

// ─── Admin: Articles ──────────────────────────────────────────────────────────

const ADMIN_LIST_ARTICLES_QUERY = gql`
  query AdminListArticles(
    $search: String
    $category: String
    $featured: Boolean
    $limit: Int
    $offset: Int
  ) {
    adminListArticles(
      search: $search
      category: $category
      featured: $featured
      limit: $limit
      offset: $offset
    ) {
      items {
        id
        slug
        title
        excerpt
        image
        tags
        publish_date
        category
        featured
        content
        author {
          id
          artist_name
          slug
          avatar
        }
      }
      total
    }
  }
`;

export async function adminListArticles(
  opts: {
    search?: string;
    category?: string;
    featured?: boolean;
    limit?: number;
    offset?: number;
  } = {},
  fetchFn?: typeof globalThis.fetch,
  token?: string,
): Promise<{ items: Article[]; total: number }> {
  return loggedApiCall("adminListArticles", async () => {
    const client = token ? getAuthClient(token) : getGraphQLClient(fetchFn);
    const data = await client.request<{ adminListArticles: { items: Article[]; total: number } }>(
      ADMIN_LIST_ARTICLES_QUERY,
      opts,
    );
    return data.adminListArticles;
  });
}

const ADMIN_GET_ARTICLE_QUERY = gql`
  query AdminGetArticle($id: String!) {
    adminGetArticle(id: $id) {
      id
      slug
      title
      excerpt
      content
      image
      tags
      publish_date
      category
      featured
      author {
        id
        artist_name
        slug
        avatar
      }
    }
  }
`;

export async function adminGetArticle(
  id: string,
  fetchFn?: typeof globalThis.fetch,
  token?: string,
): Promise<Article | null> {
  return loggedApiCall("adminGetArticle", async () => {
    const client = token ? getAuthClient(token) : getGraphQLClient(fetchFn);
    const data = await client.request<{ adminGetArticle: Article | null }>(
      ADMIN_GET_ARTICLE_QUERY,
      { id },
    );
    return data.adminGetArticle;
  });
}

const CREATE_ARTICLE_MUTATION = gql`
  mutation CreateArticle(
    $title: String!
    $slug: String!
    $excerpt: String
    $content: String
    $imageId: String
    $tags: [String!]
    $category: String
    $featured: Boolean
    $publishDate: String
  ) {
    createArticle(
      title: $title
      slug: $slug
      excerpt: $excerpt
      content: $content
      imageId: $imageId
      tags: $tags
      category: $category
      featured: $featured
      publishDate: $publishDate
    ) {
      id
      slug
      title
    }
  }
`;

export async function createArticle(input: {
  title: string;
  slug: string;
  excerpt?: string;
  content?: string;
  imageId?: string;
  tags?: string[];
  category?: string;
  featured?: boolean;
  publishDate?: string;
}) {
  return loggedApiCall(
    "createArticle",
    async () => {
      const data = await getGraphQLClient().request<{ createArticle: Article }>(
        CREATE_ARTICLE_MUTATION,
        input,
      );
      return data.createArticle;
    },
    { title: input.title },
  );
}

const UPDATE_ARTICLE_MUTATION = gql`
  mutation UpdateArticle(
    $id: String!
    $title: String
    $slug: String
    $excerpt: String
    $content: String
    $imageId: String
    $authorId: String
    $tags: [String!]
    $category: String
    $featured: Boolean
    $publishDate: String
  ) {
    updateArticle(
      id: $id
      title: $title
      slug: $slug
      excerpt: $excerpt
      content: $content
      imageId: $imageId
      authorId: $authorId
      tags: $tags
      category: $category
      featured: $featured
      publishDate: $publishDate
    ) {
      id
      slug
      title
    }
  }
`;

export async function updateArticle(input: {
  id: string;
  title?: string;
  slug?: string;
  excerpt?: string;
  content?: string;
  imageId?: string;
  authorId?: string | null;
  tags?: string[];
  category?: string;
  featured?: boolean;
  publishDate?: string;
}) {
  return loggedApiCall(
    "updateArticle",
    async () => {
      const data = await getGraphQLClient().request<{ updateArticle: Article | null }>(
        UPDATE_ARTICLE_MUTATION,
        input,
      );
      return data.updateArticle;
    },
    { id: input.id },
  );
}

const DELETE_ARTICLE_MUTATION = gql`
  mutation DeleteArticle($id: String!) {
    deleteArticle(id: $id)
  }
`;

export async function deleteArticle(id: string) {
  return loggedApiCall(
    "deleteArticle",
    async () => {
      await getGraphQLClient().request(DELETE_ARTICLE_MUTATION, { id });
    },
    { id },
  );
}

// ─── Analytics ───────────────────────────────────────────────────────────────

const ANALYTICS_QUERY = gql`
  query GetAnalytics {
    analytics
  }
`;

export async function getAnalytics(fetchFn?: typeof globalThis.fetch, token?: string) {
  return loggedApiCall(
    "getAnalytics",
    async () => {
      const client = token ? getAuthClient(token, fetchFn) : getGraphQLClient(fetchFn);
      const data = await client.request<{ analytics: Analytics | null }>(ANALYTICS_QUERY);
      return data.analytics;
    },
    {},
  );
}

// ─── Admin: Comments ──────────────────────────────────────────────────────────

const ADMIN_LIST_COMMENTS_QUERY = gql`
  query AdminListComments($search: String, $limit: Int, $offset: Int) {
    adminListComments(search: $search, limit: $limit, offset: $offset) {
      items {
        id
        collection
        item_id
        comment
        user_id
        date_created
        user {
          id
          artist_name
          avatar
        }
      }
      total
    }
  }
`;

export async function adminListComments(
  opts: { search?: string; limit?: number; offset?: number } = {},
  fetchFn?: typeof globalThis.fetch,
  token?: string,
): Promise<{ items: Comment[]; total: number }> {
  return loggedApiCall("adminListComments", async () => {
    const client = token ? getAuthClient(token) : getGraphQLClient(fetchFn);
    const data = await client.request<{ adminListComments: { items: Comment[]; total: number } }>(
      ADMIN_LIST_COMMENTS_QUERY,
      opts,
    );
    return data.adminListComments;
  });
}

// ─── Admin: Recordings ────────────────────────────────────────────────────────

const ADMIN_LIST_RECORDINGS_QUERY = gql`
  query AdminListRecordings($search: String, $status: String, $limit: Int, $offset: Int) {
    adminListRecordings(search: $search, status: $status, limit: $limit, offset: $offset) {
      items {
        id
        title
        slug
        status
        duration
        public
        featured
        user_id
        date_created
      }
      total
    }
  }
`;

export async function adminListRecordings(
  opts: { search?: string; status?: string; limit?: number; offset?: number } = {},
  fetchFn?: typeof globalThis.fetch,
  token?: string,
): Promise<{ items: Recording[]; total: number }> {
  return loggedApiCall("adminListRecordings", async () => {
    const client = token ? getAuthClient(token) : getGraphQLClient(fetchFn);
    const data = await client.request<{
      adminListRecordings: { items: Recording[]; total: number };
    }>(ADMIN_LIST_RECORDINGS_QUERY, opts);
    return data.adminListRecordings;
  });
}

const ADMIN_DELETE_RECORDING_MUTATION = gql`
  mutation AdminDeleteRecording($id: String!) {
    adminDeleteRecording(id: $id)
  }
`;

export async function adminDeleteRecording(id: string): Promise<void> {
  return loggedApiCall("adminDeleteRecording", async () => {
    await getGraphQLClient().request(ADMIN_DELETE_RECORDING_MUTATION, { id });
  });
}

// --- Queues ---

export type JobCounts = {
  waiting: number;
  active: number;
  completed: number;
  failed: number;
  delayed: number;
  paused: number;
};

export type QueueInfo = {
  name: string;
  counts: JobCounts;
  isPaused: boolean;
};

export type Job = {
  id: string;
  name: string;
  queue: string;
  status: string;
  data: Record<string, unknown>;
  result: unknown;
  failedReason: string | null;
  attemptsMade: number;
  createdAt: string;
  processedAt: string | null;
  finishedAt: string | null;
  progress: number | null;
};

const ADMIN_QUEUES_QUERY = gql`
  query AdminQueues {
    adminQueues {
      name
      isPaused
      counts {
        waiting
        active
        completed
        failed
        delayed
        paused
      }
    }
  }
`;

export async function getAdminQueues(
  fetchFn?: typeof globalThis.fetch,
  token?: string,
): Promise<QueueInfo[]> {
  return loggedApiCall("getAdminQueues", async () => {
    const client = token ? getAuthClient(token, fetchFn) : getGraphQLClient(fetchFn);
    const data = await client.request<{ adminQueues: QueueInfo[] }>(ADMIN_QUEUES_QUERY);
    return data.adminQueues;
  });
}

const ADMIN_QUEUE_JOBS_QUERY = gql`
  query AdminQueueJobs($queue: String!, $status: String, $limit: Int, $offset: Int) {
    adminQueueJobs(queue: $queue, status: $status, limit: $limit, offset: $offset) {
      id
      name
      queue
      status
      data
      result
      failedReason
      attemptsMade
      createdAt
      processedAt
      finishedAt
      progress
    }
  }
`;

export async function getAdminQueueJobs(
  queue: string,
  status?: string,
  limit?: number,
  offset?: number,
  fetchFn?: typeof globalThis.fetch,
  token?: string,
): Promise<Job[]> {
  return loggedApiCall("getAdminQueueJobs", async () => {
    const client = token ? getAuthClient(token, fetchFn) : getGraphQLClient(fetchFn);
    const data = await client.request<{ adminQueueJobs: Job[] }>(ADMIN_QUEUE_JOBS_QUERY, {
      queue,
      status,
      limit,
      offset,
    });
    return data.adminQueueJobs;
  });
}

const ADMIN_RETRY_JOB_MUTATION = gql`
  mutation AdminRetryJob($queue: String!, $jobId: String!) {
    adminRetryJob(queue: $queue, jobId: $jobId)
  }
`;

export async function adminRetryJob(queue: string, jobId: string): Promise<void> {
  return loggedApiCall("adminRetryJob", async () => {
    await getGraphQLClient().request(ADMIN_RETRY_JOB_MUTATION, { queue, jobId });
  });
}

const ADMIN_REMOVE_JOB_MUTATION = gql`
  mutation AdminRemoveJob($queue: String!, $jobId: String!) {
    adminRemoveJob(queue: $queue, jobId: $jobId)
  }
`;

export async function adminRemoveJob(queue: string, jobId: string): Promise<void> {
  return loggedApiCall("adminRemoveJob", async () => {
    await getGraphQLClient().request(ADMIN_REMOVE_JOB_MUTATION, { queue, jobId });
  });
}

const ADMIN_PAUSE_QUEUE_MUTATION = gql`
  mutation AdminPauseQueue($queue: String!) {
    adminPauseQueue(queue: $queue)
  }
`;

const ADMIN_RESUME_QUEUE_MUTATION = gql`
  mutation AdminResumeQueue($queue: String!) {
    adminResumeQueue(queue: $queue)
  }
`;

export async function adminPauseQueue(queue: string): Promise<void> {
  return loggedApiCall("adminPauseQueue", async () => {
    await getGraphQLClient().request(ADMIN_PAUSE_QUEUE_MUTATION, { queue });
  });
}

export async function adminResumeQueue(queue: string): Promise<void> {
  return loggedApiCall("adminResumeQueue", async () => {
    await getGraphQLClient().request(ADMIN_RESUME_QUEUE_MUTATION, { queue });
  });
}
