import { Request, Response, NextFunction, Router } from 'express';
import { ProductsService } from '../services/products.service';
import {
  CreateProductDto,
  UpdateProductDto,
  CreateProductCategoryDto,
  UpdateProductCategoryDto,
} from '../dto';

export class ProductsController {
  public router: Router;

  constructor(private readonly productsService: ProductsService) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // Products
    this.router.get('/', this.findAll.bind(this));
    this.router.get('/sellable', this.getSellableProducts.bind(this));
    this.router.get('/purchasable', this.getPurchasableProducts.bind(this));
    this.router.get('/:id', this.findOne.bind(this));
    this.router.get('/sku/:sku', this.findBySku.bind(this));
    this.router.get('/barcode/:barcode', this.findByBarcode.bind(this));
    this.router.post('/', this.create.bind(this));
    this.router.patch('/:id', this.update.bind(this));
    this.router.delete('/:id', this.delete.bind(this));
  }

  private async findAll(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const {
        search,
        categoryId,
        productType,
        isActive,
        isSellable,
        isPurchasable,
        limit,
        offset,
      } = req.query;

      const result = await this.productsService.findAll({
        tenantId,
        search: search as string,
        categoryId: categoryId as string,
        productType: productType as 'product' | 'service' | 'consumable' | 'kit',
        isActive: isActive ? isActive === 'true' : undefined,
        isSellable: isSellable ? isSellable === 'true' : undefined,
        isPurchasable: isPurchasable ? isPurchasable === 'true' : undefined,
        limit: limit ? parseInt(limit as string, 10) : undefined,
        offset: offset ? parseInt(offset as string, 10) : undefined,
      });

      res.json(result);
    } catch (error) {
      next(error);
    }
  }

  private async findOne(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const product = await this.productsService.findOne(id, tenantId);

      if (!product) {
        res.status(404).json({ error: 'Product not found' });
        return;
      }

      res.json({ data: product });
    } catch (error) {
      next(error);
    }
  }

  private async findBySku(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { sku } = req.params;
      const product = await this.productsService.findBySku(sku, tenantId);

      if (!product) {
        res.status(404).json({ error: 'Product not found' });
        return;
      }

      res.json({ data: product });
    } catch (error) {
      next(error);
    }
  }

  private async findByBarcode(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { barcode } = req.params;
      const product = await this.productsService.findByBarcode(barcode, tenantId);

      if (!product) {
        res.status(404).json({ error: 'Product not found' });
        return;
      }

      res.json({ data: product });
    } catch (error) {
      next(error);
    }
  }

  private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const dto: CreateProductDto = req.body;
      const product = await this.productsService.create(tenantId, dto, userId);
      res.status(201).json({ data: product });
    } catch (error) {
      next(error);
    }
  }

  private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const dto: UpdateProductDto = req.body;
      const product = await this.productsService.update(id, tenantId, dto, userId);

      if (!product) {
        res.status(404).json({ error: 'Product not found' });
        return;
      }

      res.json({ data: product });
    } catch (error) {
      next(error);
    }
  }

  private async delete(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const deleted = await this.productsService.delete(id, tenantId);

      if (!deleted) {
        res.status(404).json({ error: 'Product not found' });
        return;
      }

      res.status(204).send();
    } catch (error) {
      next(error);
    }
  }

  private async getSellableProducts(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const products = await this.productsService.getSellableProducts(tenantId);
      res.json({ data: products });
    } catch (error) {
      next(error);
    }
  }

  private async getPurchasableProducts(
    req: Request,
    res: Response,
    next: NextFunction
  ): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const products = await this.productsService.getPurchasableProducts(tenantId);
      res.json({ data: products });
    } catch (error) {
      next(error);
    }
  }
}

export class CategoriesController {
  public router: Router;

  constructor(private readonly productsService: ProductsService) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    this.router.get('/', this.findAll.bind(this));
    this.router.get('/tree', this.getCategoryTree.bind(this));
    this.router.get('/:id', this.findOne.bind(this));
    this.router.post('/', this.create.bind(this));
    this.router.patch('/:id', this.update.bind(this));
    this.router.delete('/:id', this.delete.bind(this));
  }

  private async findAll(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { search, parentId, isActive, limit, offset } = req.query;

      const result = await this.productsService.findAllCategories({
        tenantId,
        search: search as string,
        parentId: parentId as string,
        isActive: isActive ? isActive === 'true' : undefined,
        limit: limit ? parseInt(limit as string, 10) : undefined,
        offset: offset ? parseInt(offset as string, 10) : undefined,
      });

      res.json(result);
    } catch (error) {
      next(error);
    }
  }

  private async findOne(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const category = await this.productsService.findCategory(id, tenantId);

      if (!category) {
        res.status(404).json({ error: 'Category not found' });
        return;
      }

      res.json({ data: category });
    } catch (error) {
      next(error);
    }
  }

  private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const dto: CreateProductCategoryDto = req.body;
      const category = await this.productsService.createCategory(tenantId, dto);
      res.status(201).json({ data: category });
    } catch (error) {
      next(error);
    }
  }

  private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const dto: UpdateProductCategoryDto = req.body;
      const category = await this.productsService.updateCategory(id, tenantId, dto);

      if (!category) {
        res.status(404).json({ error: 'Category not found' });
        return;
      }

      res.json({ data: category });
    } catch (error) {
      next(error);
    }
  }

  private async delete(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const deleted = await this.productsService.deleteCategory(id, tenantId);

      if (!deleted) {
        res.status(404).json({ error: 'Category not found' });
        return;
      }

      res.status(204).send();
    } catch (error) {
      next(error);
    }
  }

  private async getCategoryTree(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const categories = await this.productsService.getCategoryTree(tenantId);
      res.json({ data: categories });
    } catch (error) {
      next(error);
    }
  }
}
