/**
 * Warehouse Zone Service
 * ERP Construccion - Modulo Warehouses
 *
 * Logica de negocio para gestion de zonas dentro de almacenes.
 * Las zonas permiten organizar el almacen en areas funcionales.
 */

import { Repository, DataSource } from 'typeorm';
import { WarehouseZone } from '../entities/warehouse-zone.entity';
import { Warehouse } from '../entities/warehouse.entity';
import { PaginationOptions, PaginatedResult, ServiceContext } from './types';

// DTOs
export interface CreateWarehouseZoneDto {
  warehouseId: string;
  code: string;
  name: string;
  description?: string;
  color?: string;
  zoneType?: 'storage' | 'picking' | 'packing' | 'shipping' | 'receiving' | 'quarantine';
}

export interface UpdateWarehouseZoneDto extends Partial<Omit<CreateWarehouseZoneDto, 'warehouseId'>> {
  isActive?: boolean;
}

export interface WarehouseZoneFilters {
  warehouseId?: string;
  zoneType?: 'storage' | 'picking' | 'packing' | 'shipping' | 'receiving' | 'quarantine';
  isActive?: boolean;
  search?: string;
}

export class WarehouseZoneService {
  private zoneRepository: Repository<WarehouseZone>;
  private warehouseRepository: Repository<Warehouse>;

  constructor(dataSource: DataSource) {
    this.zoneRepository = dataSource.getRepository(WarehouseZone);
    this.warehouseRepository = dataSource.getRepository(Warehouse);
  }

  /**
   * Validate warehouse belongs to tenant
   */
  private async validateWarehouse(ctx: ServiceContext, warehouseId: string): Promise<Warehouse> {
    const warehouse = await this.warehouseRepository.findOne({
      where: { id: warehouseId, tenantId: ctx.tenantId },
    });

    if (!warehouse) {
      throw new Error('Warehouse not found or access denied');
    }

    return warehouse;
  }

  /**
   * Create a new zone
   */
  async create(ctx: ServiceContext, dto: CreateWarehouseZoneDto): Promise<WarehouseZone> {
    // Validate warehouse access
    await this.validateWarehouse(ctx, dto.warehouseId);

    // Check code uniqueness within warehouse
    const existing = await this.zoneRepository.findOne({
      where: {
        warehouseId: dto.warehouseId,
        code: dto.code,
      },
    });

    if (existing) {
      throw new Error(`Zone with code ${dto.code} already exists in this warehouse`);
    }

    const zone = this.zoneRepository.create({
      ...dto,
      zoneType: dto.zoneType || 'storage',
    });

    return this.zoneRepository.save(zone);
  }

  /**
   * Find zone by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<WarehouseZone | null> {
    const zone = await this.zoneRepository.findOne({
      where: { id },
      relations: ['warehouse'],
    });

    if (!zone) return null;

    // Validate tenant access through warehouse
    if (zone.warehouse.tenantId !== ctx.tenantId) {
      return null;
    }

    return zone;
  }

  /**
   * Find zone by code within warehouse
   */
  async findByCode(ctx: ServiceContext, warehouseId: string, code: string): Promise<WarehouseZone | null> {
    await this.validateWarehouse(ctx, warehouseId);

    return this.zoneRepository.findOne({
      where: { warehouseId, code },
      relations: ['warehouse'],
    });
  }

  /**
   * List zones for a warehouse
   */
  async findByWarehouse(
    ctx: ServiceContext,
    warehouseId: string,
    filters: Omit<WarehouseZoneFilters, 'warehouseId'> = {}
  ): Promise<WarehouseZone[]> {
    await this.validateWarehouse(ctx, warehouseId);

    const queryBuilder = this.zoneRepository
      .createQueryBuilder('zone')
      .where('zone.warehouse_id = :warehouseId', { warehouseId });

    if (filters.zoneType) {
      queryBuilder.andWhere('zone.zone_type = :zoneType', { zoneType: filters.zoneType });
    }
    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('zone.is_active = :isActive', { isActive: filters.isActive });
    }
    if (filters.search) {
      queryBuilder.andWhere('(zone.code ILIKE :search OR zone.name ILIKE :search)', {
        search: `%${filters.search}%`,
      });
    }

    return queryBuilder.orderBy('zone.name', 'ASC').getMany();
  }

  /**
   * List all zones with filters and pagination
   */
  async findAll(
    ctx: ServiceContext,
    filters: WarehouseZoneFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<WarehouseZone>> {
    const queryBuilder = this.zoneRepository
      .createQueryBuilder('zone')
      .innerJoin('zone.warehouse', 'warehouse')
      .addSelect(['warehouse.id', 'warehouse.code', 'warehouse.name', 'warehouse.tenantId'])
      .where('warehouse.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    if (filters.warehouseId) {
      queryBuilder.andWhere('zone.warehouse_id = :warehouseId', { warehouseId: filters.warehouseId });
    }
    if (filters.zoneType) {
      queryBuilder.andWhere('zone.zone_type = :zoneType', { zoneType: filters.zoneType });
    }
    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('zone.is_active = :isActive', { isActive: filters.isActive });
    }
    if (filters.search) {
      queryBuilder.andWhere('(zone.code ILIKE :search OR zone.name ILIKE :search)', {
        search: `%${filters.search}%`,
      });
    }

    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await queryBuilder
      .orderBy('warehouse.name', 'ASC')
      .addOrderBy('zone.name', 'ASC')
      .skip(skip)
      .take(pagination.limit)
      .getManyAndCount();

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  /**
   * Get active zones for dropdown
   */
  async getActiveZones(ctx: ServiceContext, warehouseId: string): Promise<WarehouseZone[]> {
    await this.validateWarehouse(ctx, warehouseId);

    return this.zoneRepository.find({
      where: {
        warehouseId,
        isActive: true,
      },
      order: { name: 'ASC' },
      select: ['id', 'code', 'name', 'zoneType', 'color'],
    });
  }

  /**
   * Update zone
   */
  async update(ctx: ServiceContext, id: string, dto: UpdateWarehouseZoneDto): Promise<WarehouseZone | null> {
    const zone = await this.findById(ctx, id);
    if (!zone) return null;

    Object.assign(zone, dto);
    return this.zoneRepository.save(zone);
  }

  /**
   * Activate/Deactivate zone
   */
  async setActive(ctx: ServiceContext, id: string, isActive: boolean): Promise<WarehouseZone | null> {
    return this.update(ctx, id, { isActive });
  }

  /**
   * Delete zone
   */
  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    const zone = await this.findById(ctx, id);
    if (!zone) return false;

    const result = await this.zoneRepository.delete({ id });
    return (result.affected ?? 0) > 0;
  }

  /**
   * Get zone statistics for a warehouse
   */
  async getStatistics(ctx: ServiceContext, warehouseId: string): Promise<{
    total: number;
    active: number;
    byType: Record<string, number>;
  }> {
    await this.validateWarehouse(ctx, warehouseId);

    const [total, active, byTypeRaw] = await Promise.all([
      this.zoneRepository.count({ where: { warehouseId } }),
      this.zoneRepository.count({ where: { warehouseId, isActive: true } }),

      this.zoneRepository
        .createQueryBuilder('zone')
        .select('zone.zone_type', 'type')
        .addSelect('COUNT(*)', 'count')
        .where('zone.warehouse_id = :warehouseId', { warehouseId })
        .groupBy('zone.zone_type')
        .getRawMany(),
    ]);

    const byType: Record<string, number> = {};
    byTypeRaw.forEach((row: { type: string; count: string }) => {
      byType[row.type] = parseInt(row.count, 10);
    });

    return {
      total,
      active,
      byType,
    };
  }

  /**
   * Check if zone code is available within warehouse
   */
  async isCodeAvailable(
    ctx: ServiceContext,
    warehouseId: string,
    code: string,
    excludeId?: string
  ): Promise<boolean> {
    await this.validateWarehouse(ctx, warehouseId);

    const queryBuilder = this.zoneRepository
      .createQueryBuilder('zone')
      .where('zone.warehouse_id = :warehouseId', { warehouseId })
      .andWhere('zone.code = :code', { code });

    if (excludeId) {
      queryBuilder.andWhere('zone.id != :excludeId', { excludeId });
    }

    const count = await queryBuilder.getCount();
    return count === 0;
  }

  /**
   * Bulk create zones for a warehouse
   */
  async bulkCreate(
    ctx: ServiceContext,
    warehouseId: string,
    zones: Omit<CreateWarehouseZoneDto, 'warehouseId'>[]
  ): Promise<WarehouseZone[]> {
    await this.validateWarehouse(ctx, warehouseId);

    const zoneEntities = zones.map((z) =>
      this.zoneRepository.create({
        ...z,
        warehouseId,
        zoneType: z.zoneType || 'storage',
      })
    );

    return this.zoneRepository.save(zoneEntities);
  }
}
