import { Request, Response } from 'express';
import ServiceCategory from '../schema/service-category.schema';
import ServiceItem from '../schema/service-item.schema';
import { buildTextSearch } from '../../../helpers/query.helper';
import { formatBookingServiceValue } from '../../../helpers/booking-services.helper';

function slugify(value: string) {
  return String(value || '')
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '');
}

function mapBookingServices(
  categories: Array<{ name: string; services: Array<{ name: string; _id?: unknown }> }>
) {
  return categories.flatMap((category) =>
    category.services.map((service) => ({
      category: category.name,
      item: service.name,
      value: formatBookingServiceValue(category.name, service.name),
      serviceId: service._id,
      categoryId: (category as { _id?: unknown })._id,
    }))
  );
}

export class ServicesController {
  static async listPublic(_req: Request, res: Response) {
    try {
      const categories = await ServiceCategory.find({ isPublished: true })
        .sort({ sortOrder: 1, name: 1 })
        .lean();

      const categoryIds = categories.map((category) => category._id);
      const items = await ServiceItem.find({
        categoryId: { $in: categoryIds },
        isPublished: true,
      })
        .sort({ sortOrder: 1, name: 1 })
        .lean();

      const itemsByCategory = new Map<string, typeof items>();
      items.forEach((item) => {
        const key = String(item.categoryId);
        const bucket = itemsByCategory.get(key) || [];
        bucket.push(item);
        itemsByCategory.set(key, bucket);
      });

      const response = categories.map((category) => ({
        ...category,
        services: itemsByCategory.get(String(category._id)) || [],
      }));

      return res.status(200).json({
        success: true,
        response,
        bookingServices: mapBookingServices(
          response.map((category) => ({
            ...category,
            services: (category.services as Array<{ name: string; _id?: unknown }>).map((service) => ({
              name: service.name,
              _id: service._id,
            })),
          }))
        ),
      });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async listAdmin(req: Request, res: Response) {
    try {
      const filter: Record<string, unknown> = {};
      const published = String(req.query.published || '').trim();
      if (published === 'true') filter.isPublished = true;
      if (published === 'false') filter.isPublished = false;

      const search = buildTextSearch(['name', 'description'], String(req.query.q || ''));
      if (search.$or) Object.assign(filter, search);

      const categories = await ServiceCategory.find(filter).sort({ sortOrder: 1, name: 1 }).lean();
      const categoryIds = categories.map((category) => category._id);
      const items = await ServiceItem.find({ categoryId: { $in: categoryIds } })
        .sort({ sortOrder: 1, name: 1 })
        .lean();

      const itemsByCategory = new Map<string, typeof items>();
      items.forEach((item) => {
        const key = String(item.categoryId);
        const bucket = itemsByCategory.get(key) || [];
        bucket.push(item);
        itemsByCategory.set(key, bucket);
      });

      const response = categories.map((category) => ({
        ...category,
        services: itemsByCategory.get(String(category._id)) || [],
      }));

      return res.status(200).json({ success: true, response });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async createCategory(req: Request, res: Response) {
    try {
      const name = String(req.body?.name || '').trim();
      if (!name) {
        return res.status(400).json({ success: false, message: 'Category name is required' });
      }

      const slug = slugify(req.body?.slug || name);
      const category = await ServiceCategory.create({
        name,
        slug,
        imageUrl: String(req.body?.imageUrl || '').trim(),
        description: String(req.body?.description || '').trim(),
        sortOrder: Number(req.body?.sortOrder ?? 0),
        isPublished: req.body?.isPublished !== false,
      });

      return res.status(201).json({ success: true, response: category });
    } catch (error: any) {
      if (error?.code === 11000) {
        return res.status(409).json({ success: false, message: 'Category already exists' });
      }
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async updateCategory(req: Request, res: Response) {
    try {
      const updates: Record<string, unknown> = {};
      if (req.body?.name !== undefined) updates.name = String(req.body.name).trim();
      if (req.body?.slug !== undefined) updates.slug = slugify(String(req.body.slug));
      if (req.body?.imageUrl !== undefined) updates.imageUrl = String(req.body.imageUrl).trim();
      if (req.body?.description !== undefined) updates.description = String(req.body.description).trim();
      if (req.body?.sortOrder !== undefined) updates.sortOrder = Number(req.body.sortOrder);
      if (req.body?.isPublished !== undefined) updates.isPublished = Boolean(req.body.isPublished);

      const category = await ServiceCategory.findByIdAndUpdate(req.params.id, updates, {
        new: true,
        runValidators: true,
      });

      if (!category) {
        return res.status(404).json({ success: false, message: 'Category not found' });
      }

      return res.status(200).json({ success: true, response: category });
    } catch (error: any) {
      if (error?.code === 11000) {
        return res.status(409).json({ success: false, message: 'Category already exists' });
      }
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async deleteCategory(req: Request, res: Response) {
    try {
      const category = await ServiceCategory.findByIdAndDelete(req.params.id);
      if (!category) {
        return res.status(404).json({ success: false, message: 'Category not found' });
      }

      await ServiceItem.deleteMany({ categoryId: category._id });
      return res.status(200).json({ success: true, message: 'Category deleted' });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async createItem(req: Request, res: Response) {
    try {
      const categoryId = req.params.categoryId;
      const category = await ServiceCategory.findById(categoryId);
      if (!category) {
        return res.status(404).json({ success: false, message: 'Category not found' });
      }

      const name = String(req.body?.name || '').trim();
      if (!name) {
        return res.status(400).json({ success: false, message: 'Service name is required' });
      }

      const item = await ServiceItem.create({
        categoryId,
        name,
        sortOrder: Number(req.body?.sortOrder ?? 0),
        isPublished: req.body?.isPublished !== false,
      });

      return res.status(201).json({ success: true, response: item });
    } catch (error: any) {
      if (error?.code === 11000) {
        return res.status(409).json({ success: false, message: 'Service already exists in this category' });
      }
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async updateItem(req: Request, res: Response) {
    try {
      const updates: Record<string, unknown> = {};
      if (req.body?.name !== undefined) updates.name = String(req.body.name).trim();
      if (req.body?.sortOrder !== undefined) updates.sortOrder = Number(req.body.sortOrder);
      if (req.body?.isPublished !== undefined) updates.isPublished = Boolean(req.body.isPublished);
      if (req.body?.categoryId !== undefined) updates.categoryId = req.body.categoryId;

      const item = await ServiceItem.findByIdAndUpdate(req.params.id, updates, {
        new: true,
        runValidators: true,
      });

      if (!item) {
        return res.status(404).json({ success: false, message: 'Service not found' });
      }

      return res.status(200).json({ success: true, response: item });
    } catch (error: any) {
      if (error?.code === 11000) {
        return res.status(409).json({ success: false, message: 'Service already exists in this category' });
      }
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async deleteItem(req: Request, res: Response) {
    try {
      const item = await ServiceItem.findByIdAndDelete(req.params.id);
      if (!item) {
        return res.status(404).json({ success: false, message: 'Service not found' });
      }

      return res.status(200).json({ success: true, message: 'Service deleted' });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }
}
