import { Request, Response, NextFunction } from 'express';
import { PgRestaurantGroupRepository, PgRestaurantBrandRepository, PgRestaurantOutletRepository } from '../../infrastructure/repositories/postgresql/restaurant.repository';
import { ApiError } from '../../utils/errors/api.error';
import logger from '../../config/logger';

// Initialize repositories
const groupRepository = new PgRestaurantGroupRepository();
const brandRepository = new PgRestaurantBrandRepository();
const outletRepository = new PgRestaurantOutletRepository();

/**
 * Restaurant Controller
 * Handles CRUD operations for restaurant groups, brands, and outlets
 */
export class RestaurantController {
  /**
   * Get all restaurant groups with pagination
   * @route GET /api/restaurants/groups
   */
  async getAllGroups(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const page = parseInt(req.query.page as string) || 1;
      const limit = parseInt(req.query.limit as string) || 10;

      // Extract filter parameters
      const filters: Record<string, any> = {};
      if (req.query.name) {
        filters.name = `%${req.query.name}%`;
      }

      // Get data with pagination
      const groups = await groupRepository.findAll(page, limit, filters);
      const totalCount = await groupRepository.count(filters);

      // Calculate pagination metadata
      const totalPages = Math.ceil(totalCount / limit);
      const hasNext = page < totalPages;
      const hasPrev = page > 1;

      res.status(200).json({
        status: 'success',
        data: {
          groups,
          pagination: {
            page,
            limit,
            totalCount,
            totalPages,
            hasNext,
            hasPrev
          }
        }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Get a restaurant group by ID
   * @route GET /api/restaurants/groups/:id
   */
  async getGroupById(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const groupId = req.params.id;
      const group = await groupRepository.findById(groupId);

      if (!group) {
        throw ApiError.notFound(`Restaurant group with ID ${groupId} not found`);
      }

      res.status(200).json({
        status: 'success',
        data: { group }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Create a new restaurant group
   * @route POST /api/restaurants/groups
   */
  async createGroup(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const groupData = req.body;

      // Create the group
      const newGroup = await groupRepository.create(groupData);

      res.status(201).json({
        status: 'success',
        data: { group: newGroup }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Update a restaurant group
   * @route PUT /api/restaurants/groups/:id
   */
  async updateGroup(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const groupId = req.params.id;
      const groupData = req.body;

      // Check if group exists
      const existingGroup = await groupRepository.findById(groupId);
      if (!existingGroup) {
        throw ApiError.notFound(`Restaurant group with ID ${groupId} not found`);
      }

      // Update the group
      const updatedGroup = await groupRepository.update(groupId, groupData);

      res.status(200).json({
        status: 'success',
        data: { group: updatedGroup }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Delete a restaurant group
   * @route DELETE /api/restaurants/groups/:id
   */
  async deleteGroup(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const groupId = req.params.id;

      // Check if group exists
      const existingGroup = await groupRepository.findById(groupId);
      if (!existingGroup) {
        throw ApiError.notFound(`Restaurant group with ID ${groupId} not found`);
      }

      // Start a transaction
      const trx = await groupRepository.beginTransaction();

      try {
        // Get all brands for this group
        const brands = await brandRepository.findByGroupId(groupId);

        // For each brand, delete all outlets
        for (const brand of brands) {
          const outlets = await outletRepository.findByBrandId(brand.id);
          for (const outlet of outlets) {
            await outletRepository.delete(outlet.id);
          }
          // Delete the brand
          await brandRepository.delete(brand.id);
        }

        // Delete the group
        await groupRepository.delete(groupId);

        // Commit the transaction
        await groupRepository.commitTransaction(trx);

        res.status(200).json({
          status: 'success',
          message: `Restaurant group with ID ${groupId} has been deleted`
        });
      } catch (error) {
        // Rollback the transaction in case of error
        await groupRepository.rollbackTransaction(trx);
        throw error;
      }
    } catch (error) {
      next(error);
    }
  }

  /**
   * Get full restaurant hierarchy for a group
   * @route GET /api/restaurants/groups/:id/hierarchy
   */
  async getGroupHierarchy(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const groupId = req.params.id;

      // Check if group exists
      const existingGroup = await groupRepository.findById(groupId);
      if (!existingGroup) {
        throw ApiError.notFound(`Restaurant group with ID ${groupId} not found`);
      }

      // Get the full hierarchy
      const hierarchy = await groupRepository.getFullHierarchy(groupId);

      res.status(200).json({
        status: 'success',
        data: { hierarchy }
      });
    } catch (error) {
      next(error);
    }
  }

  // Brand-related methods

  /**
   * Get all brands for a group
   * @route GET /api/restaurants/groups/:groupId/brands
   */
  async getBrandsByGroupId(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const groupId = req.params.groupId;

      // Check if group exists
      const existingGroup = await groupRepository.findById(groupId);
      if (!existingGroup) {
        throw ApiError.notFound(`Restaurant group with ID ${groupId} not found`);
      }

      // Get all brands for this group
      const brands = await brandRepository.findByGroupId(groupId);

      res.status(200).json({
        status: 'success',
        data: { brands }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Create a new brand for a group
   * @route POST /api/restaurants/groups/:groupId/brands
   */
  async createBrand(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const groupId = req.params.groupId;
      const brandData = { ...req.body, group_id: groupId };

      // Check if group exists
      const existingGroup = await groupRepository.findById(groupId);
      if (!existingGroup) {
        throw ApiError.notFound(`Restaurant group with ID ${groupId} not found`);
      }

      // Create the brand
      const newBrand = await brandRepository.create(brandData);

      res.status(201).json({
        status: 'success',
        data: { brand: newBrand }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Update a brand
   * @route PUT /api/restaurants/brands/:id
   */
  async updateBrand(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const brandId = req.params.id;
      const brandData = req.body;

      // Check if brand exists
      const existingBrand = await brandRepository.findById(brandId);
      if (!existingBrand) {
        throw ApiError.notFound(`Restaurant brand with ID ${brandId} not found`);
      }

      // Update the brand
      const updatedBrand = await brandRepository.update(brandId, brandData);

      res.status(200).json({
        status: 'success',
        data: { brand: updatedBrand }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Delete a brand
   * @route DELETE /api/restaurants/brands/:id
   */
  async deleteBrand(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const brandId = req.params.id;

      // Check if brand exists
      const existingBrand = await brandRepository.findById(brandId);
      if (!existingBrand) {
        throw ApiError.notFound(`Restaurant brand with ID ${brandId} not found`);
      }

      // Start a transaction
      const trx = await brandRepository.beginTransaction();

      try {
        // Delete all outlets for this brand
        const outlets = await outletRepository.findByBrandId(brandId);
        for (const outlet of outlets) {
          await outletRepository.delete(outlet.id);
        }

        // Delete the brand
        await brandRepository.delete(brandId);

        // Commit the transaction
        await brandRepository.commitTransaction(trx);

        res.status(200).json({
          status: 'success',
          message: `Restaurant brand with ID ${brandId} has been deleted`
        });
      } catch (error) {
        // Rollback the transaction in case of error
        await brandRepository.rollbackTransaction(trx);
        throw error;
      }
    } catch (error) {
      next(error);
    }
  }

  // Outlet-related methods

  /**
   * Get all outlets for a brand
   * @route GET /api/restaurants/brands/:brandId/outlets
   */
  async getOutletsByBrandId(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const brandId = req.params.brandId;

      // Check if brand exists
      const existingBrand = await brandRepository.findById(brandId);
      if (!existingBrand) {
        throw ApiError.notFound(`Restaurant brand with ID ${brandId} not found`);
      }

      // Get all outlets for this brand
      const outlets = await outletRepository.findByBrandId(brandId);

      res.status(200).json({
        status: 'success',
        data: { outlets }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Create a new outlet for a brand
   * @route POST /api/restaurants/brands/:brandId/outlets
   */
  async createOutlet(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const brandId = req.params.brandId;
      const outletData = { ...req.body, brand_id: brandId };

      // Check if brand exists
      const existingBrand = await brandRepository.findById(brandId);
      if (!existingBrand) {
        throw ApiError.notFound(`Restaurant brand with ID ${brandId} not found`);
      }

      // Check if POS ID already exists
      if (outletData.pos_id) {
        const existingOutlet = await outletRepository.findByPosId(outletData.pos_id);
        if (existingOutlet) {
          throw ApiError.conflict(`An outlet with POS ID ${outletData.pos_id} already exists`);
        }
      }

      // Create the outlet
      const newOutlet = await outletRepository.create(outletData);

      res.status(201).json({
        status: 'success',
        data: { outlet: newOutlet }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Update an outlet
   * @route PUT /api/restaurants/outlets/:id
   */
  async updateOutlet(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const outletId = req.params.id;
      const outletData = req.body;

      // Check if outlet exists
      const existingOutlet = await outletRepository.findById(outletId);
      if (!existingOutlet) {
        throw ApiError.notFound(`Restaurant outlet with ID ${outletId} not found`);
      }

      // Check if POS ID is being changed and if it already exists
      if (outletData.pos_id && outletData.pos_id !== existingOutlet.pos_id) {
        const existingOutletWithPosId = await outletRepository.findByPosId(outletData.pos_id);
        if (existingOutletWithPosId && existingOutletWithPosId.id !== outletId) {
          throw ApiError.conflict(`An outlet with POS ID ${outletData.pos_id} already exists`);
        }
      }

      // Update the outlet
      const updatedOutlet = await outletRepository.update(outletId, outletData);

      res.status(200).json({
        status: 'success',
        data: { outlet: updatedOutlet }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Delete an outlet
   * @route DELETE /api/restaurants/outlets/:id
   */
  async deleteOutlet(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const outletId = req.params.id;

      // Check if outlet exists
      const existingOutlet = await outletRepository.findById(outletId);
      if (!existingOutlet) {
        throw ApiError.notFound(`Restaurant outlet with ID ${outletId} not found`);
      }

      // Delete the outlet
      await outletRepository.delete(outletId);

      res.status(200).json({
        status: 'success',
        message: `Restaurant outlet with ID ${outletId} has been deleted`
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Find an outlet by POS ID
   * @route GET /api/restaurants/outlets/pos/:posId
   */
  async getOutletByPosId(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const posId = req.params.posId;

      // Find outlet by POS ID
      const outlet = await outletRepository.findByPosId(posId);

      if (!outlet) {
        throw ApiError.notFound(`Restaurant outlet with POS ID ${posId} not found`);
      }

      res.status(200).json({
        status: 'success',
        data: { outlet }
      });
    } catch (error) {
      next(error);
    }
  }
}

// Export controller instance
export const restaurantController = new RestaurantController();
