import { Repository, IsNull } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Warehouse } from './entities/warehouse.entity.js';
import { Location } from './entities/location.entity.js';
import { StockQuant } from './entities/stock-quant.entity.js';
import { NotFoundError, ValidationError, ConflictError } from '../../shared/types/index.js';
import { logger } from '../../shared/utils/logger.js';

// ===== Interfaces =====

export interface CreateWarehouseDto {
  companyId: string;
  name: string;
  code: string;
  addressId?: string;
  isDefault?: boolean;
}

export interface UpdateWarehouseDto {
  name?: string;
  addressId?: string | null;
  isDefault?: boolean;
  active?: boolean;
}

export interface WarehouseFilters {
  companyId?: string;
  active?: boolean;
  page?: number;
  limit?: number;
}

export interface WarehouseWithRelations extends Warehouse {
  companyName?: string;
}

// ===== Service Class =====

class WarehousesService {
  private warehouseRepository: Repository<Warehouse>;
  private locationRepository: Repository<Location>;
  private stockQuantRepository: Repository<StockQuant>;

  constructor() {
    this.warehouseRepository = AppDataSource.getRepository(Warehouse);
    this.locationRepository = AppDataSource.getRepository(Location);
    this.stockQuantRepository = AppDataSource.getRepository(StockQuant);
  }

  async findAll(
    tenantId: string,
    filters: WarehouseFilters = {}
  ): Promise<{ data: WarehouseWithRelations[]; total: number }> {
    try {
      const { companyId, active, page = 1, limit = 50 } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.warehouseRepository
        .createQueryBuilder('warehouse')
        .leftJoinAndSelect('warehouse.company', 'company')
        .where('warehouse.tenantId = :tenantId', { tenantId });

      if (companyId) {
        queryBuilder.andWhere('warehouse.companyId = :companyId', { companyId });
      }

      if (active !== undefined) {
        queryBuilder.andWhere('warehouse.active = :active', { active });
      }

      const total = await queryBuilder.getCount();

      const warehouses = await queryBuilder
        .orderBy('warehouse.name', 'ASC')
        .skip(skip)
        .take(limit)
        .getMany();

      const data: WarehouseWithRelations[] = warehouses.map(w => ({
        ...w,
        companyName: w.company?.name,
      }));

      logger.debug('Warehouses retrieved', { tenantId, count: data.length, total });

      return { data, total };
    } catch (error) {
      logger.error('Error retrieving warehouses', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  async findById(id: string, tenantId: string): Promise<WarehouseWithRelations> {
    try {
      const warehouse = await this.warehouseRepository
        .createQueryBuilder('warehouse')
        .leftJoinAndSelect('warehouse.company', 'company')
        .where('warehouse.id = :id', { id })
        .andWhere('warehouse.tenantId = :tenantId', { tenantId })
        .getOne();

      if (!warehouse) {
        throw new NotFoundError('Almacén no encontrado');
      }

      return {
        ...warehouse,
        companyName: warehouse.company?.name,
      };
    } catch (error) {
      logger.error('Error finding warehouse', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  async create(dto: CreateWarehouseDto, tenantId: string, userId: string): Promise<Warehouse> {
    try {
      // Check unique code within company
      const existing = await this.warehouseRepository.findOne({
        where: {
          companyId: dto.companyId,
          code: dto.code,
        },
      });

      if (existing) {
        throw new ConflictError(`Ya existe un almacén con código ${dto.code} en esta empresa`);
      }

      // If is_default, clear other defaults for company
      if (dto.isDefault) {
        await this.warehouseRepository.update(
          { companyId: dto.companyId, tenantId },
          { isDefault: false }
        );
      }

      const warehouse = this.warehouseRepository.create({
        tenantId,
        companyId: dto.companyId,
        name: dto.name,
        code: dto.code,
        addressId: dto.addressId || null,
        isDefault: dto.isDefault || false,
        createdBy: userId,
      });

      await this.warehouseRepository.save(warehouse);

      logger.info('Warehouse created', {
        warehouseId: warehouse.id,
        tenantId,
        name: warehouse.name,
        createdBy: userId,
      });

      return warehouse;
    } catch (error) {
      logger.error('Error creating warehouse', {
        error: (error as Error).message,
        tenantId,
        dto,
      });
      throw error;
    }
  }

  async update(id: string, dto: UpdateWarehouseDto, tenantId: string, userId: string): Promise<Warehouse> {
    try {
      const existing = await this.findById(id, tenantId);

      // If setting as default, clear other defaults
      if (dto.isDefault) {
        await this.warehouseRepository
          .createQueryBuilder()
          .update(Warehouse)
          .set({ isDefault: false })
          .where('companyId = :companyId', { companyId: existing.companyId })
          .andWhere('tenantId = :tenantId', { tenantId })
          .andWhere('id != :id', { id })
          .execute();
      }

      if (dto.name !== undefined) existing.name = dto.name;
      if (dto.addressId !== undefined) existing.addressId = dto.addressId;
      if (dto.isDefault !== undefined) existing.isDefault = dto.isDefault;
      if (dto.active !== undefined) existing.active = dto.active;

      existing.updatedBy = userId;
      existing.updatedAt = new Date();

      await this.warehouseRepository.save(existing);

      logger.info('Warehouse updated', {
        warehouseId: id,
        tenantId,
        updatedBy: userId,
      });

      return await this.findById(id, tenantId);
    } catch (error) {
      logger.error('Error updating warehouse', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  async delete(id: string, tenantId: string): Promise<void> {
    try {
      await this.findById(id, tenantId);

      // Check if warehouse has locations with stock
      const hasStock = await this.stockQuantRepository
        .createQueryBuilder('sq')
        .innerJoin('sq.location', 'location')
        .where('location.warehouseId = :warehouseId', { warehouseId: id })
        .andWhere('sq.quantity > 0')
        .getCount();

      if (hasStock > 0) {
        throw new ConflictError('No se puede eliminar un almacén que tiene stock');
      }

      await this.warehouseRepository.delete({ id, tenantId });

      logger.info('Warehouse deleted', {
        warehouseId: id,
        tenantId,
      });
    } catch (error) {
      logger.error('Error deleting warehouse', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  async getLocations(warehouseId: string, tenantId: string): Promise<Location[]> {
    await this.findById(warehouseId, tenantId);

    return this.locationRepository.find({
      where: {
        warehouseId,
        tenantId,
      },
      order: { name: 'ASC' },
    });
  }

  async getStock(warehouseId: string, tenantId: string): Promise<any[]> {
    await this.findById(warehouseId, tenantId);

    const stock = await this.stockQuantRepository
      .createQueryBuilder('sq')
      .innerJoinAndSelect('sq.product', 'product')
      .innerJoinAndSelect('sq.location', 'location')
      .where('location.warehouseId = :warehouseId', { warehouseId })
      .orderBy('product.name', 'ASC')
      .addOrderBy('location.name', 'ASC')
      .getMany();

    return stock.map(sq => ({
      ...sq,
      productName: sq.product?.name,
      productCode: sq.product?.code,
      locationName: sq.location?.name,
    }));
  }
}

export const warehousesService = new WarehousesService();
