/**
 * Warehouse Location Service
 * ERP Construccion - Modulo Warehouses
 *
 * Logica de negocio para gestion de ubicaciones dentro de almacenes.
 * Las ubicaciones representan posiciones fisicas (pasillos, racks, estantes, bins).
 */

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

// DTOs
export interface CreateWarehouseLocationDto {
  warehouseId: string;
  parentId?: string;
  code: string;
  name: string;
  barcode?: string;
  locationType?: 'zone' | 'aisle' | 'rack' | 'shelf' | 'bin';
  aisle?: string;
  rack?: string;
  shelf?: string;
  bin?: string;
  capacityUnits?: number;
  capacityVolume?: number;
  capacityWeight?: number;
  allowedProductTypes?: string[];
  temperatureRange?: { min?: number; max?: number };
  humidityRange?: { min?: number; max?: number };
  isPickable?: boolean;
  isReceivable?: boolean;
}

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

export interface WarehouseLocationFilters {
  warehouseId?: string;
  parentId?: string;
  locationType?: 'zone' | 'aisle' | 'rack' | 'shelf' | 'bin';
  isActive?: boolean;
  isPickable?: boolean;
  isReceivable?: boolean;
  search?: string;
}

export class WarehouseLocationService {
  private locationRepository: Repository<WarehouseLocation>;
  private warehouseRepository: Repository<Warehouse>;

  constructor(dataSource: DataSource) {
    this.locationRepository = dataSource.getRepository(WarehouseLocation);
    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;
  }

  /**
   * Build hierarchy path from parent
   */
  private async buildHierarchyPath(
    warehouseId: string,
    parentId?: string
  ): Promise<{ path: string; level: number }> {
    if (!parentId) {
      return { path: '', level: 0 };
    }

    const parent = await this.locationRepository.findOne({
      where: { id: parentId, warehouseId },
    });

    if (!parent) {
      throw new Error('Parent location not found');
    }

    const path = parent.hierarchyPath ? `${parent.hierarchyPath}/${parent.id}` : parent.id;
    const level = parent.hierarchyLevel + 1;

    return { path, level };
  }

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

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

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

    // Build hierarchy
    const { path, level } = await this.buildHierarchyPath(dto.warehouseId, dto.parentId);

    const location = this.locationRepository.create({
      ...dto,
      locationType: dto.locationType || 'shelf',
      hierarchyPath: path,
      hierarchyLevel: level,
      allowedProductTypes: dto.allowedProductTypes || [],
    });

    return this.locationRepository.save(location);
  }

  /**
   * Find location by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<WarehouseLocation | null> {
    const location = await this.locationRepository.findOne({
      where: { id, deletedAt: IsNull() },
      relations: ['warehouse', 'parent'],
    });

    if (!location) return null;

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

    return location;
  }

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

    return this.locationRepository.findOne({
      where: { warehouseId, code, deletedAt: IsNull() },
      relations: ['warehouse', 'parent'],
    });
  }

  /**
   * Find location by barcode
   */
  async findByBarcode(ctx: ServiceContext, barcode: string): Promise<WarehouseLocation | null> {
    const location = await this.locationRepository.findOne({
      where: { barcode, deletedAt: IsNull() },
      relations: ['warehouse'],
    });

    if (!location) return null;

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

    return location;
  }

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

    const queryBuilder = this.locationRepository
      .createQueryBuilder('location')
      .leftJoinAndSelect('location.parent', 'parent')
      .where('location.warehouse_id = :warehouseId', { warehouseId })
      .andWhere('location.deleted_at IS NULL');

    if (filters.parentId !== undefined) {
      if (filters.parentId === null || filters.parentId === '') {
        queryBuilder.andWhere('location.parent_id IS NULL');
      } else {
        queryBuilder.andWhere('location.parent_id = :parentId', { parentId: filters.parentId });
      }
    }
    if (filters.locationType) {
      queryBuilder.andWhere('location.location_type = :locationType', {
        locationType: filters.locationType,
      });
    }
    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('location.is_active = :isActive', { isActive: filters.isActive });
    }
    if (filters.isPickable !== undefined) {
      queryBuilder.andWhere('location.is_pickable = :isPickable', { isPickable: filters.isPickable });
    }
    if (filters.isReceivable !== undefined) {
      queryBuilder.andWhere('location.is_receivable = :isReceivable', {
        isReceivable: filters.isReceivable,
      });
    }
    if (filters.search) {
      queryBuilder.andWhere(
        '(location.code ILIKE :search OR location.name ILIKE :search OR location.barcode ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    return queryBuilder
      .orderBy('location.hierarchy_level', 'ASC')
      .addOrderBy('location.code', 'ASC')
      .getMany();
  }

  /**
   * Get root locations (no parent) for a warehouse
   */
  async getRootLocations(ctx: ServiceContext, warehouseId: string): Promise<WarehouseLocation[]> {
    await this.validateWarehouse(ctx, warehouseId);

    return this.locationRepository.find({
      where: {
        warehouseId,
        parentId: IsNull(),
        deletedAt: IsNull(),
        isActive: true,
      },
      order: { code: 'ASC' },
    });
  }

  /**
   * Get child locations
   */
  async getChildren(ctx: ServiceContext, parentId: string): Promise<WarehouseLocation[]> {
    const parent = await this.findById(ctx, parentId);
    if (!parent) return [];

    return this.locationRepository.find({
      where: {
        parentId,
        deletedAt: IsNull(),
      },
      order: { code: 'ASC' },
    });
  }

  /**
   * Get location tree for a warehouse
   */
  async getTree(ctx: ServiceContext, warehouseId: string): Promise<WarehouseLocation[]> {
    await this.validateWarehouse(ctx, warehouseId);

    return this.locationRepository.find({
      where: {
        warehouseId,
        deletedAt: IsNull(),
        isActive: true,
      },
      order: { hierarchyLevel: 'ASC', code: 'ASC' },
    });
  }

  /**
   * List all locations with filters and pagination
   */
  async findAll(
    ctx: ServiceContext,
    filters: WarehouseLocationFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 50 }
  ): Promise<PaginatedResult<WarehouseLocation>> {
    const queryBuilder = this.locationRepository
      .createQueryBuilder('location')
      .innerJoin('location.warehouse', 'warehouse')
      .addSelect(['warehouse.id', 'warehouse.code', 'warehouse.name', 'warehouse.tenantId'])
      .leftJoinAndSelect('location.parent', 'parent')
      .where('warehouse.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('location.deleted_at IS NULL');

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

    if (filters.warehouseId) {
      queryBuilder.andWhere('location.warehouse_id = :warehouseId', {
        warehouseId: filters.warehouseId,
      });
    }
    if (filters.parentId !== undefined) {
      if (filters.parentId === null || filters.parentId === '') {
        queryBuilder.andWhere('location.parent_id IS NULL');
      } else {
        queryBuilder.andWhere('location.parent_id = :parentId', { parentId: filters.parentId });
      }
    }
    if (filters.locationType) {
      queryBuilder.andWhere('location.location_type = :locationType', {
        locationType: filters.locationType,
      });
    }
    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('location.is_active = :isActive', { isActive: filters.isActive });
    }
    if (filters.isPickable !== undefined) {
      queryBuilder.andWhere('location.is_pickable = :isPickable', { isPickable: filters.isPickable });
    }
    if (filters.isReceivable !== undefined) {
      queryBuilder.andWhere('location.is_receivable = :isReceivable', {
        isReceivable: filters.isReceivable,
      });
    }
    if (filters.search) {
      queryBuilder.andWhere(
        '(location.code ILIKE :search OR location.name ILIKE :search OR location.barcode ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

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

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

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

  /**
   * Get pickable locations for a warehouse
   */
  async getPickableLocations(ctx: ServiceContext, warehouseId: string): Promise<WarehouseLocation[]> {
    await this.validateWarehouse(ctx, warehouseId);

    return this.locationRepository.find({
      where: {
        warehouseId,
        isActive: true,
        isPickable: true,
        deletedAt: IsNull(),
      },
      order: { code: 'ASC' },
    });
  }

  /**
   * Get receivable locations for a warehouse
   */
  async getReceivableLocations(ctx: ServiceContext, warehouseId: string): Promise<WarehouseLocation[]> {
    await this.validateWarehouse(ctx, warehouseId);

    return this.locationRepository.find({
      where: {
        warehouseId,
        isActive: true,
        isReceivable: true,
        deletedAt: IsNull(),
      },
      order: { code: 'ASC' },
    });
  }

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

    // If parent changed, rebuild hierarchy
    if (dto.parentId !== undefined && dto.parentId !== location.parentId) {
      const { path, level } = await this.buildHierarchyPath(location.warehouseId, dto.parentId);
      location.hierarchyPath = path;
      location.hierarchyLevel = level;
    }

    Object.assign(location, dto);
    return this.locationRepository.save(location);
  }

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

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

    // Check for children
    const childCount = await this.locationRepository.count({
      where: { parentId: id, deletedAt: IsNull() },
    });

    if (childCount > 0) {
      throw new Error('Cannot delete location with child locations. Delete children first.');
    }

    const result = await this.locationRepository.update(
      { id },
      { deletedAt: new Date(), isActive: false }
    );
    return (result.affected ?? 0) > 0;
  }

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

    const baseWhere = { warehouseId, deletedAt: IsNull() };

    const [total, active, pickable, receivable, byTypeRaw, capacityResult] = await Promise.all([
      this.locationRepository.count({ where: baseWhere }),
      this.locationRepository.count({ where: { ...baseWhere, isActive: true } }),
      this.locationRepository.count({ where: { ...baseWhere, isPickable: true, isActive: true } }),
      this.locationRepository.count({ where: { ...baseWhere, isReceivable: true, isActive: true } }),

      this.locationRepository
        .createQueryBuilder('location')
        .select('location.location_type', 'type')
        .addSelect('COUNT(*)', 'count')
        .where('location.warehouse_id = :warehouseId', { warehouseId })
        .andWhere('location.deleted_at IS NULL')
        .groupBy('location.location_type')
        .getRawMany(),

      this.locationRepository
        .createQueryBuilder('location')
        .select('SUM(location.capacity_units)', 'total')
        .where('location.warehouse_id = :warehouseId', { warehouseId })
        .andWhere('location.deleted_at IS NULL')
        .andWhere('location.is_active = true')
        .getRawOne(),
    ]);

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

    return {
      total,
      active,
      byType,
      pickable,
      receivable,
      totalCapacityUnits: parseInt(capacityResult?.total) || 0,
    };
  }

  /**
   * Search locations
   */
  async search(ctx: ServiceContext, query: string, warehouseId?: string, limit = 10): Promise<WarehouseLocation[]> {
    const queryBuilder = this.locationRepository
      .createQueryBuilder('location')
      .innerJoin('location.warehouse', 'warehouse')
      .where('warehouse.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('location.deleted_at IS NULL')
      .andWhere('location.is_active = true')
      .andWhere(
        '(location.code ILIKE :query OR location.name ILIKE :query OR location.barcode ILIKE :query)',
        { query: `%${query}%` }
      );

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

    if (warehouseId) {
      queryBuilder.andWhere('location.warehouse_id = :warehouseId', { warehouseId });
    }

    return queryBuilder
      .orderBy('location.code', 'ASC')
      .take(limit)
      .getMany();
  }

  /**
   * Check if location 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.locationRepository
      .createQueryBuilder('location')
      .where('location.warehouse_id = :warehouseId', { warehouseId })
      .andWhere('location.code = :code', { code })
      .andWhere('location.deleted_at IS NULL');

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

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

  /**
   * Bulk create locations
   */
  async bulkCreate(
    ctx: ServiceContext,
    warehouseId: string,
    locations: Omit<CreateWarehouseLocationDto, 'warehouseId'>[]
  ): Promise<WarehouseLocation[]> {
    await this.validateWarehouse(ctx, warehouseId);

    const locationEntities: WarehouseLocation[] = [];

    for (const loc of locations) {
      const { path, level } = await this.buildHierarchyPath(warehouseId, loc.parentId);

      locationEntities.push(
        this.locationRepository.create({
          ...loc,
          warehouseId,
          locationType: loc.locationType || 'shelf',
          hierarchyPath: path,
          hierarchyLevel: level,
          allowedProductTypes: loc.allowedProductTypes || [],
        })
      );
    }

    return this.locationRepository.save(locationEntities);
  }

  /**
   * Generate location code based on coordinates
   */
  generateLocationCode(aisle?: string, rack?: string, shelf?: string, bin?: string): string {
    const parts = [aisle, rack, shelf, bin].filter(Boolean);
    return parts.join('-');
  }
}
