import { Request, Response } from 'express';
import { BookStatus, Prisma } from '@prisma/client';
import crypto from 'crypto';
import prisma from '../config/database';
import { sendError, sendSuccess } from '../utils/response.utils';
import { AuthenticatedRequest } from '../middleware/auth.middleware';
import { operationalSku, slugify } from '../utils/catalog.utils';

const includeBookRelations = {
  images: { orderBy: { sortOrder: 'asc' as const } },
  publisher: true,
  subCategory: { include: { category: true } },
  bookAuthors: { include: { author: true } },
  bookCategories: { include: { category: true } },
  inventory: true,
  variants: { where: { isActive: true }, orderBy: [{ isDefault: 'desc' as const }, { format: 'asc' as const }] },
  catalogues: { include: { catalogue: true } },
} satisfies Prisma.BookInclude;

type BookWithRelations = Prisma.BookGetPayload<{ include: typeof includeBookRelations }>;

const formatBook = (book: BookWithRelations, includeAttributes = false) => ({
  id: book.id,
  sourceProductId: book.sourceProductId,
  title: book.title,
  slug: book.slug,
  sourceSlug: book.sourceSlug,
  isbn: book.isbn,
  sku: book.sku,
  sourceSku: book.sourceSku,
  barcode: book.barcode,
  description: book.description,
  descriptionHtml: book.descriptionHtml,
  additionalAttributesText: book.additionalAttributesText,
  additionalAttributesHtml: book.additionalAttributesHtml,
  price: Number(book.price),
  discountPrice: book.discountPrice === null ? null : Number(book.discountPrice),
  gstRate: Number(book.gstRate),
  stock: book.inventory?.stock ?? book.stock,
  format: book.format,
  binding: book.binding,
  language: book.language,
  sourceLanguage: book.sourceLanguage,
  edition: book.edition,
  classGrade: book.classGrade,
  publicationYear: book.publicationYear,
  publicationDate: book.publicationDate,
  pageCount: book.pageCount,
  pages: book.pagesText ?? (book.pageCount === null ? null : String(book.pageCount)),
  weight: book.weight === null ? null : Number(book.weight),
  dimensions: book.dimensions,
  coverImage: book.coverImage,
  metaTitle: book.metaTitle,
  metaDescription: book.metaDescription,
  status: book.status,
  isActive: book.isActive,
  displayOrder: book.displayOrder,
  isNewRelease: book.isNewRelease,
  isFeatured: book.isFeatured,
  isTrending: book.isTrending,
  isBestseller: book.isBestseller,
  ratingAverage: Number(book.ratingAverage),
  ratingCount: book.ratingCount,
  publisherId: book.publisherId,
  publisherName: book.publisher?.name ?? null,
  authorName: book.bookAuthors[0]?.author.name ?? null,
  authors: book.bookAuthors.map(({ author }) => ({ id: author.id, name: author.name, slug: author.slug })),
  categoryName: book.bookCategories[0]?.category.name ?? null,
  categories: book.bookCategories.map(({ category }) => ({
    id: category.id,
    name: category.name,
    slug: category.slug,
  })),
  images: book.images.map((image) => ({
    id: image.id,
    url: image.url,
    isPrimary: image.isPrimary,
    sortOrder: image.sortOrder,
    assetStatus: image.assetStatus,
  })),
  variants: (book.variants || []).map((v) => ({
    id: v.id,
    bookId: v.bookId,
    title: v.title,
    format: v.format,
    isbn: v.isbn,
    sku: v.sku,
    barcode: v.barcode,
    price: Number(v.price),
    discountPrice: v.discountPrice === null ? null : Number(v.discountPrice),
    stock: v.stock,
    weight: v.weight === null ? null : Number(v.weight),
    dimensions: v.dimensions,
    coverImage: v.coverImage,
    digitalFileUrl: v.digitalFileUrl,
    digitalFormat: v.digitalFormat,
    attributes: v.attributes,
    isDefault: v.isDefault,
    isActive: v.isActive,
  })),
  catalogues: book.catalogues.map(({ catalogue }) => ({
    id: catalogue.id,
    title: catalogue.title,
    pdfUrl: catalogue.pdfUrl,
    assetStatus: catalogue.assetStatus,
  })),
  createdAt: book.createdAt,
  updatedAt: book.updatedAt,
  ...(includeAttributes && 'attributes' in book ? { attributes: (book as any).attributes } : {}),
});

const getUniqueSlug = async (requested: string, currentBookId?: string): Promise<string> => {
  const base = slugify(requested) || 'book';
  let candidate = base.slice(0, 191);
  let sequence = 2;
  while (
    await prisma.book.findFirst({
      where: { slug: candidate, ...(currentBookId ? { id: { not: currentBookId } } : {}) },
      select: { id: true },
    })
  ) {
    const suffix = `-${sequence}`;
    candidate = `${base.slice(0, 191 - suffix.length)}${suffix}`;
    sequence += 1;
  }
  return candidate;
};

export const getBooks = async (req: Request, res: Response) => {
  try {
    const {
      search,
      category,
      author,
      publisher,
      language,
      status,
      isFeatured,
      isTrending,
      isBestseller,
      minPrice,
      maxPrice,
      sortBy = 'displayOrder',
      sortOrder = 'asc',
      limit = '20',
      page = '1',
      ids,
    } = req.query;

    const take = Math.max(1, Math.min(100, Number(limit) || 20));
    const currentPage = Math.max(1, Number(page) || 1);
    const skip = (currentPage - 1) * take;
    const where: Prisma.BookWhereInput = {};

    if (!status) where.isActive = true;
    else if (String(status).toUpperCase() !== 'ALL') {
      where.status = String(status).toUpperCase() === 'DISABLED' ? BookStatus.DISABLED : BookStatus.ENABLED;
    }
    if (ids) {
      const idList = String(ids).split(',').filter(Boolean);
      if (idList.length > 0) {
        where.id = { in: idList };
      }
    }
    if (search) {
      const query = String(search).trim();
      where.OR = [
        { title: { contains: query } },
        { description: { contains: query } },
        { isbn: { contains: query } },
        { sku: { contains: query } },
        { sourceSku: { contains: query } },
        { publisher: { name: { contains: query } } },
        { bookAuthors: { some: { author: { name: { contains: query } } } } },
        { bookCategories: { some: { category: { name: { contains: query } } } } },
        { attributes: { some: { OR: [{ name: { contains: query } }, { value: { contains: query } }] } } },
      ];
    }
    if (category) {
      const value = String(category);
      where.bookCategories = {
        some: { category: { OR: [{ id: value }, { slug: value }, { name: value }] } },
      };
    }
    if (author) {
      const value = String(author);
      where.bookAuthors = { some: { author: { OR: [{ id: value }, { slug: value }, { name: value }] } } };
    }
    if (publisher) {
      const value = String(publisher);
      where.publisher = { OR: [{ id: value }, { slug: value }, { name: value }] };
    }
    if (language) where.language = String(language);
    if (isFeatured === 'true') where.isFeatured = true;
    if (isTrending === 'true') where.isTrending = true;
    if (isBestseller === 'true') where.isBestseller = true;
    if (minPrice || maxPrice) {
      where.price = {};
      if (minPrice) where.price.gte = Number(minPrice);
      if (maxPrice) where.price.lte = Number(maxPrice);
    }

    const sortableFields = new Set([
      'displayOrder',
      'createdAt',
      'updatedAt',
      'title',
      'price',
      'stock',
      'ratingAverage',
    ]);
    const orderField = sortableFields.has(String(sortBy)) ? String(sortBy) : 'displayOrder';
    const direction: Prisma.SortOrder = sortOrder === 'desc' ? 'desc' : 'asc';

    const [books, total] = await Promise.all([
      prisma.book.findMany({
        where,
        take,
        skip,
        include: includeBookRelations,
        orderBy: { [orderField]: direction },
      }),
      prisma.book.count({ where }),
    ]);

    return sendSuccess(res, 'Books retrieved successfully', {
      books: books.map((book) => formatBook(book)),
      pagination: {
        total,
        page: currentPage,
        limit: take,
        totalPages: Math.max(1, Math.ceil(total / take)),
      },
    });
  } catch (error) {
    return sendError(res, 'Error fetching books', error instanceof Error ? error.message : String(error), 500);
  }
};

export const getBookBySlug = async (req: Request, res: Response) => {
  try {
    const value = req.params.slug;
    const book = await prisma.book.findFirst({
      where: { OR: [{ slug: value }, { id: value }, { sourceProductId: value }] },
      include: {
        ...includeBookRelations,
        attributes: { orderBy: [{ sortOrder: 'asc' }, { sourceRow: 'asc' }] },
        texts: { orderBy: [{ field: 'asc' }, { partNumber: 'asc' }] },
        reviews: { include: { user: true }, take: 10, orderBy: { createdAt: 'desc' } },
      },
    });
    if (!book) return sendError(res, 'Book not found', null, 404);
    return sendSuccess(res, 'Book detail retrieved', { book: formatBook(book as any, true) });
  } catch (error) {
    return sendError(res, 'Error fetching book detail', error instanceof Error ? error.message : String(error), 500);
  }
};

export const createBook = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const input = req.body;
    const slug = await getUniqueSlug(input.slug || input.title);
    const requestedSku: string | null = input.sku || input.isbn || null;
    const existingSku = requestedSku
      ? await prisma.book.findUnique({ where: { sku: requestedSku } })
      : null;
    const sku = existingSku && requestedSku
      ? operationalSku(requestedSku, crypto.randomUUID().slice(0, 8), 2)
      : requestedSku;
    const book = await prisma.$transaction(async (tx) =>
      tx.book.create({
        data: {
          title: input.title,
          slug,
          isbn: input.isbn,
          sku,
          barcode: input.barcode ?? null,
          description: input.description,
          price: input.price,
          discountPrice: input.discountPrice ?? null,
          gstRate: input.gstRate ?? 0,
          stock: input.stock ?? 0,
          format: input.format ?? 'PAPERBACK',
          binding: input.binding ?? input.format ?? null,
          language: input.language ?? 'UNKNOWN',
          edition: input.edition ?? null,
          weight: input.weight ?? null,
          dimensions: input.dimensions ?? null,
          publisherId: input.publisherId ?? null,
          subCategoryId: input.subCategoryId ?? null,
          publicationDate: input.publicationDate ? new Date(input.publicationDate) : null,
          pageCount: input.pageCount ?? null,
          pagesText: input.pagesText ?? (input.pageCount ? String(input.pageCount) : null),
          coverImage: input.coverImage ?? input.imageUrls?.[0] ?? null,
          metaTitle: input.metaTitle ?? null,
          metaDescription: input.metaDescription ?? null,
          status: input.status === 'DISABLED' ? BookStatus.DISABLED : BookStatus.ENABLED,
          isActive: input.status !== 'DISABLED',
          displayOrder: input.displayOrder ?? 0,
          isNewRelease: input.isNewRelease ?? false,
          isFeatured: input.isFeatured ?? false,
          isTrending: input.isTrending ?? false,
          isBestseller: input.isBestseller ?? false,
          inventory: { create: { stock: input.stock ?? 0, reserved: 0 } },
          images: {
            create: (input.imageUrls ?? []).map((url: string, index: number) => ({
              url,
              storageKey: decodeURIComponent(url.replace(/^\/assets\//, '')),
              assetStatus: 'DOWNLOADED',
              sortOrder: index,
              isPrimary: index === 0,
            })),
          },
          bookAuthors: { create: (input.authorIds ?? []).map((authorId: string) => ({ authorId })) },
          bookCategories: {
            create: (input.categoryIds ?? []).map((categoryId: string) => ({ categoryId })),
          },
          ...(input.variants && input.variants.length > 0 && {
            variants: {
              create: input.variants.map((v: any, index: number) => ({
                title: v.title || `${input.title} (${v.format})`,
                format: v.format || 'PAPERBACK',
                isbn: v.isbn || null,
                sku: v.sku || `${sku}-${v.format.toLowerCase()}`,
                barcode: v.barcode || null,
                price: v.price || input.price,
                discountPrice: v.discountPrice ?? null,
                stock: v.stock !== undefined ? v.stock : input.stock ?? 0,
                weight: v.weight ?? null,
                dimensions: v.dimensions ?? null,
                coverImage: v.coverImage ?? null,
                digitalFileUrl: v.digitalFileUrl ?? null,
                digitalFormat: v.digitalFormat ?? null,
                attributes: v.attributes ?? undefined,
                isDefault: v.isDefault ?? index === 0,
                isActive: v.isActive ?? true,
              })),
            },
          }),
        },
        include: includeBookRelations,
      })
    );
    return sendSuccess(res, 'Book added to store catalog successfully', { book: formatBook(book) }, 201);
  } catch (error) {
    return sendError(res, 'Error creating book', error instanceof Error ? error.message : String(error), 500);
  }
};

export const updateBook = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const id = req.params.id;
    const input = req.body;
    const existing = await prisma.book.findUnique({ where: { id } });
    if (!existing) return sendError(res, 'Book not found', null, 404);
    const slug = input.slug || input.title ? await getUniqueSlug(input.slug || input.title, id) : undefined;

    await prisma.$transaction(async (tx) => {
      await tx.book.update({
        where: { id },
        data: {
          ...(input.title !== undefined && { title: input.title }),
          ...(slug && { slug }),
          ...(input.isbn !== undefined && { isbn: input.isbn }),
          ...(input.sku !== undefined && { sku: input.sku }),
          ...(input.barcode !== undefined && { barcode: input.barcode }),
          ...(input.description !== undefined && { description: input.description }),
          ...(input.price !== undefined && { price: input.price }),
          ...(input.discountPrice !== undefined && { discountPrice: input.discountPrice }),
          ...(input.stock !== undefined && { stock: input.stock }),
          ...(input.format !== undefined && { format: input.format }),
          ...(input.binding !== undefined && { binding: input.binding }),
          ...(input.language !== undefined && { language: input.language }),
          ...(input.edition !== undefined && { edition: input.edition }),
          ...(input.weight !== undefined && { weight: input.weight }),
          ...(input.dimensions !== undefined && { dimensions: input.dimensions }),
          ...(input.publisherId !== undefined && { publisherId: input.publisherId }),
          ...(input.pageCount !== undefined && { pageCount: input.pageCount }),
          ...(input.coverImage !== undefined && { coverImage: input.coverImage }),
          ...(input.metaTitle !== undefined && { metaTitle: input.metaTitle }),
          ...(input.metaDescription !== undefined && { metaDescription: input.metaDescription }),
          ...(input.status !== undefined && {
            status: input.status === 'DISABLED' ? BookStatus.DISABLED : BookStatus.ENABLED,
            isActive: input.status !== 'DISABLED',
          }),
          ...(input.displayOrder !== undefined && { displayOrder: input.displayOrder }),
          ...(input.isNewRelease !== undefined && { isNewRelease: input.isNewRelease }),
          ...(input.isFeatured !== undefined && { isFeatured: input.isFeatured }),
          ...(input.isTrending !== undefined && { isTrending: input.isTrending }),
          ...(input.isBestseller !== undefined && { isBestseller: input.isBestseller }),
        },
      });
      if (input.stock !== undefined) {
        await tx.bookInventory.upsert({
          where: { bookId: id },
          create: { bookId: id, stock: input.stock },
          update: { stock: input.stock },
        });
      }
      if (input.authorIds) {
        await tx.bookAuthor.deleteMany({ where: { bookId: id } });
        if (input.authorIds.length) {
          await tx.bookAuthor.createMany({
            data: input.authorIds.map((authorId: string) => ({ bookId: id, authorId })),
          });
        }
      }
      if (input.categoryIds) {
        await tx.bookCategory.deleteMany({ where: { bookId: id } });
        if (input.categoryIds.length) {
          await tx.bookCategory.createMany({
            data: input.categoryIds.map((categoryId: string) => ({ bookId: id, categoryId })),
          });
        }
      }
    });

    const book = await prisma.book.findUnique({ where: { id }, include: includeBookRelations });
    return sendSuccess(res, 'Book updated successfully', { book: book ? formatBook(book) : null });
  } catch (error) {
    return sendError(res, 'Error updating book', error instanceof Error ? error.message : String(error), 500);
  }
};

export const deleteBook = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const result = await prisma.book.updateMany({
      where: { id: req.params.id },
      data: { status: BookStatus.DISABLED, isActive: false },
    });
    if (!result.count) return sendError(res, 'Book not found', null, 404);
    return sendSuccess(res, 'Book disabled successfully');
  } catch (error) {
    return sendError(res, 'Error disabling book', error instanceof Error ? error.message : String(error), 500);
  }
};

export const bulkDeleteBooks = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const result = await prisma.book.updateMany({
      where: { id: { in: req.body.bookIds } },
      data: { status: BookStatus.DISABLED, isActive: false },
    });
    return sendSuccess(res, `Bulk disabled ${result.count} books successfully`, { count: result.count });
  } catch (error) {
    return sendError(res, 'Bulk disable failed', error instanceof Error ? error.message : String(error), 500);
  }
};

export const bulkUpdateBooks = async (req: AuthenticatedRequest, res: Response) => {
  try {
    const { bookIds, data } = req.body;
    const { stock, ...bookData } = data;
    const result = await prisma.$transaction(async (tx) => {
      const updated = await tx.book.updateMany({ where: { id: { in: bookIds } }, data: bookData });
      if (stock !== undefined) {
        await tx.book.updateMany({ where: { id: { in: bookIds } }, data: { stock } });
        await Promise.all(
          bookIds.map((bookId: string) =>
            tx.bookInventory.upsert({
              where: { bookId },
              create: { bookId, stock },
              update: { stock },
            })
          )
        );
      }
      return updated;
    });
    return sendSuccess(res, `Bulk updated ${result.count} books successfully`, { count: result.count });
  } catch (error) {
    return sendError(res, 'Bulk update failed', error instanceof Error ? error.message : String(error), 500);
  }
};

export const bulkImportBooks = async (_req: AuthenticatedRequest, res: Response) =>
  sendError(
    res,
    'Use the resumable catalog:import command for workbook imports; the legacy JSON bulk endpoint is disabled.',
    null,
    410
  );

export const exportBooks = async (_req: AuthenticatedRequest, res: Response) => {
  try {
    const books = await prisma.book.findMany({ include: includeBookRelations, orderBy: { displayOrder: 'asc' } });
    return sendSuccess(
      res,
      'Book catalog exported successfully',
      books.map((book) => formatBook(book))
    );
  } catch (error) {
    return sendError(res, 'Export failed', error instanceof Error ? error.message : String(error), 500);
  }
};
