/**
 * Component: Hardcover API Service
 * Documentation: documentation/backend/services/hardcover-sync.md
 *
 * GraphQL queries and API communication with the Hardcover platform.
 * Exports fetchHardcoverList for use by the sync orchestration layer.
 */

import axios from 'axios';
import { RMAB_USER_AGENT } from '@/lib/utils/user-agent';
import { RMABLogger } from '@/lib/utils/logger';

const logger = RMABLogger.create('HardcoverAPI');
const HARDCOVER_API_URL = 'https://api.hardcover.app/v1/graphql';

export interface HardcoverApiBook {
  bookId: string;
  title: string;
  author: string;
  coverUrl?: string;
}

/** Shape of a book node returned inside user_books or list_books from the Hardcover GraphQL API */
interface HardcoverBookNode {
  id?: number;
  title?: string;
  cached_image?: string | { url?: string };
  image?: { url?: string };
  contributions?: Array<{ author?: { name?: string } }>;
}

/** Shape of a list object returned from the Hardcover GraphQL API */
interface HardcoverListData {
  name?: string;
  list_books?: Array<{ book?: HardcoverBookNode }>;
}

const PAGE_SIZE = 100;
const MAX_PAGES = 50;

/** Extract HardcoverApiBook[] from an array of book-containing items */
function extractBooks(items: Array<{ book?: HardcoverBookNode }>): HardcoverApiBook[] {
  const books: HardcoverApiBook[] = [];
  for (const item of items) {
    const book = item.book;
    if (!book || !book.id) continue;

    const authorName =
      book.contributions?.[0]?.author?.name || 'Unknown Author';
    const cachedImg = book.cached_image;
    const coverUrl =
      (typeof cachedImg === 'string' ? cachedImg : cachedImg?.url) ||
      book.image?.url ||
      undefined;

    books.push({
      bookId: book.id.toString(),
      title: book.title || 'Unknown Title',
      author: authorName,
      coverUrl,
    });
  }
  return books;
}

/**
 * Fetch a Hardcover List using their GraphQL API.
 * This handles both 'status_id' user_books or 'list_id' list_books queries.
 * For simplicity, we assume `listId` provided by the user is an Int corresponding to a list_id or status_id.
 */
export async function fetchHardcoverList(
  apiToken: string,
  listIdStr: string,
): Promise<{ listName: string; books: HardcoverApiBook[] }> {
  // Check if it's a status list
  const isStatus = listIdStr.startsWith('status-');

  if (isStatus) {
    const statusId = parseInt(listIdStr.replace('status-', ''), 10);
    const query = `
      query GetStatusBooks($statusId: Int!, $limit: Int!, $offset: Int!) {
        me {
          user_books(where: {status_id: {_eq: $statusId}}, limit: $limit, offset: $offset, order_by: {id: desc}) {
            book {
              id
              title
              contributions {
                author {
                  name
                }
              }
              cached_image
              image {
                url
              }
            }
          }
        }
      }
    `;

    // Map status numbers to names
    const statusNames: Record<number, string> = {
      1: 'Want to Read',
      2: 'Currently Reading',
      3: 'Read',
      4: 'Did Not Finish',
    };
    const listName = statusNames[statusId] || `Status ${statusId}`;

    const allBooks: HardcoverApiBook[] = [];
    let offset = 0;
    let page = 0;

    // Paginate until fewer results than PAGE_SIZE are returned
    while (++page <= MAX_PAGES) {
      const response = await axios.post(
        HARDCOVER_API_URL,
        { query, variables: { statusId, limit: PAGE_SIZE, offset } },
        {
          headers: {
            Authorization: `Bearer ${apiToken}`,
            'Content-Type': 'application/json',
            'User-Agent': RMAB_USER_AGENT,
          },
          timeout: 30000,
        },
      );

      if (response.data?.errors) {
        throw new Error(
          `Hardcover API Error: ${response.data.errors[0]?.message}`,
        );
      }

      const userBooks: Array<{ book?: HardcoverBookNode }> =
        response.data?.data?.me?.[0]?.user_books || [];
      const pageBooks = extractBooks(userBooks);
      allBooks.push(...pageBooks);

      if (userBooks.length < PAGE_SIZE) break;
      offset += PAGE_SIZE;
    }

    return { listName, books: allBooks };
  } else {
    // Custom list query
    // - URL with @username → query that user's lists by slug
    // - Bare slug (no username) → query authenticated user's lists via `me`
    // - Numeric ID → query globally (IDs are unique)
    const isIntId = /^\d+$/.test(listIdStr);
    let extractedSlug = listIdStr;
    let extractedUsername: string | null = null;

    if (!isIntId) {
      try {
        if (listIdStr.includes('hardcover.app')) {
          const url = new URL(
            listIdStr.startsWith('http') ? listIdStr : `https://${listIdStr}`,
          );
          const parts = url.pathname.split('/').filter(Boolean);
          // URL format: /@username/lists/slug
          if (parts.length > 0) {
            extractedSlug = parts[parts.length - 1];
          }
          const userPart = parts.find((p) => p.startsWith('@'));
          if (userPart) {
            extractedUsername = userPart.slice(1);
          }
        }
      } catch (e) {
        // use extractedSlug as-is
      }
    }

    const listBookFields = `
      name
      list_books(limit: $limit, offset: $offset, order_by: {id: desc}) {
        book {
          id title cached_image image { url }
          contributions { author { name } }
        }
      }
    `;

    // Numeric ID: globally unique, query the lists table directly
    const queryById = `
      query GetListBooks($listId: Int!, $limit: Int!, $offset: Int!) {
        lists(where: {id: {_eq: $listId}}, limit: 1) {
          ${listBookFields}
        }
      }
    `;

    // Slug with username: query through the users table to scope to that user
    const queryByUserSlug = `
      query GetUserListBySlug($username: citext!, $slug: String!, $limit: Int!, $offset: Int!) {
        users(where: {username: {_eq: $username}}, limit: 1) {
          lists(where: {slug: {_eq: $slug}}, limit: 1) {
            ${listBookFields}
          }
        }
      }
    `;

    // Bare slug (no username): scope to the authenticated user via `me`
    const queryByMySlug = `
      query GetMyListBySlug($slug: String!, $limit: Int!, $offset: Int!) {
        me {
          lists(where: {slug: {_eq: $slug}}, limit: 1) {
            ${listBookFields}
          }
        }
      }
    `;

    let activeQuery: string;
    let baseVariables: Record<string, unknown>;

    if (isIntId) {
      activeQuery = queryById;
      baseVariables = { listId: parseInt(listIdStr, 10) };
    } else if (extractedUsername) {
      activeQuery = queryByUserSlug;
      baseVariables = { username: extractedUsername, slug: extractedSlug };
    } else {
      activeQuery = queryByMySlug;
      baseVariables = { slug: extractedSlug };
    }

    // First request to discover list metadata and first page of books
    const firstResponse = await axios.post(
      HARDCOVER_API_URL,
      {
        query: activeQuery,
        variables: { ...baseVariables, limit: PAGE_SIZE, offset: 0 },
      },
      {
        headers: {
          Authorization: `Bearer ${apiToken}`,
          'Content-Type': 'application/json',
        },
        timeout: 30000,
      },
    );

    if (firstResponse.data?.errors) {
      throw new Error(
        `Hardcover API Error: ${firstResponse.data.errors[0]?.message}`,
      );
    }

    // Extract lists array from the response based on which query was used
    let listsData: HardcoverListData[];
    if (isIntId) {
      listsData = firstResponse.data?.data?.lists || [];
    } else if (extractedUsername) {
      const users = firstResponse.data?.data?.users || [];
      listsData = users[0]?.lists || [];
    } else {
      listsData = firstResponse.data?.data?.me?.[0]?.lists || [];
    }

    if (listsData.length === 0) {
      let identifier: string;
      if (isIntId) {
        identifier = `ID "${listIdStr}"`;
      } else if (extractedUsername) {
        identifier = `slug "${extractedSlug}" for user @${extractedUsername}`;
      } else {
        identifier = `slug "${extractedSlug}" in your Hardcover account`;
      }
      throw new Error(`Could not find a list with ${identifier}`);
    }

    const listName = listsData[0].name || 'Hardcover List';
    const firstPageItems = listsData[0].list_books || [];
    const allBooks = extractBooks(firstPageItems);

    // Paginate if first page was full
    if (firstPageItems.length >= PAGE_SIZE) {
      let offset = PAGE_SIZE;
      let page = 1; // first page already fetched

      while (++page <= MAX_PAGES) {
        const pageResponse = await axios.post(
          HARDCOVER_API_URL,
          {
            query: activeQuery,
            variables: { ...baseVariables, limit: PAGE_SIZE, offset },
          },
          {
            headers: {
              Authorization: `Bearer ${apiToken}`,
              'Content-Type': 'application/json',
            },
            timeout: 30000,
          },
        );

        if (pageResponse.data?.errors) {
          logger.warn('Hardcover pagination interrupted by API error', {
            errors: pageResponse.data.errors,
            offset,
          });
          break;
        }

        let pageListsData: HardcoverListData[];
        if (isIntId) {
          pageListsData = pageResponse.data?.data?.lists || [];
        } else if (extractedUsername) {
          const users = pageResponse.data?.data?.users || [];
          pageListsData = users[0]?.lists || [];
        } else {
          pageListsData = pageResponse.data?.data?.me?.[0]?.lists || [];
        }

        const pageItems = pageListsData[0]?.list_books || [];
        allBooks.push(...extractBooks(pageItems));

        if (pageItems.length < PAGE_SIZE) break;
        offset += PAGE_SIZE;
      }
    }

    return { listName, books: allBooks };
  }
}
