import { Repository, FindOptionsWhere, ILike } from 'typeorm';
import { Warehouse, WarehouseLocation } from '../entities';
import {
  CreateWarehouseDto,
  UpdateWarehouseDto,
  CreateWarehouseLocationDto,
  UpdateWarehouseLocationDto,
} from '../dto';

export interface WarehouseSearchParams {
  tenantId: string;
  search?: string;
  warehouseType?: 'standard' | 'transit' | 'returns' | 'quarantine' | 'virtual';
  branchId?: string;
  isActive?: boolean;
  limit?: number;
  offset?: number;
}

export interface LocationSearchParams {
  warehouseId: string;
  search?: string;
  locationType?: 'zone' | 'aisle' | 'rack' | 'shelf' | 'bin';
  parentId?: string;
  isActive?: boolean;
  limit?: number;
  offset?: number;
}

export class WarehousesService {
  constructor(
    private readonly warehouseRepository: Repository<Warehouse>,
    private readonly locationRepository: Repository<WarehouseLocation>
  ) {}

  // ==================== Warehouses ====================

  async findAll(params: WarehouseSearchParams): Promise<{ data: Warehouse[]; total: number }> {
    const { tenantId, search, warehouseType, branchId, isActive, limit = 50, offset = 0 } = params;

    const where: FindOptionsWhere<Warehouse>[] = [];
    const baseWhere: FindOptionsWhere<Warehouse> = { tenantId };

    if (warehouseType) {
      baseWhere.warehouseType = warehouseType;
    }

    if (branchId) {
      baseWhere.branchId = branchId;
    }

    if (isActive !== undefined) {
      baseWhere.isActive = isActive;
    }

    if (search) {
      where.push(
        { ...baseWhere, name: ILike(`%${search}%`) },
        { ...baseWhere, code: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const [data, total] = await this.warehouseRepository.findAndCount({
      where,
      take: limit,
      skip: offset,
      order: { name: 'ASC' },
    });

    return { data, total };
  }

  async findOne(id: string, tenantId: string): Promise<Warehouse | null> {
    return this.warehouseRepository.findOne({ where: { id, tenantId } });
  }

  async findByCode(code: string, tenantId: string): Promise<Warehouse | null> {
    return this.warehouseRepository.findOne({ where: { code, tenantId } });
  }

  async getDefaultWarehouse(tenantId: string): Promise<Warehouse | null> {
    return this.warehouseRepository.findOne({ where: { tenantId, isDefault: true, isActive: true } });
  }

  async create(tenantId: string, dto: CreateWarehouseDto, createdBy?: string): Promise<Warehouse> {
    // Check for existing code
    const existingCode = await this.findByCode(dto.code, tenantId);
    if (existingCode) {
      throw new Error('A warehouse with this code already exists');
    }

    // If setting as default, unset other defaults
    if (dto.isDefault) {
      await this.warehouseRepository.update({ tenantId, isDefault: true }, { isDefault: false });
    }

    const warehouse = this.warehouseRepository.create({
      ...dto,
      tenantId,
      createdBy,
    });

    return this.warehouseRepository.save(warehouse);
  }

  async update(
    id: string,
    tenantId: string,
    dto: UpdateWarehouseDto,
    updatedBy?: string
  ): Promise<Warehouse | null> {
    const warehouse = await this.findOne(id, tenantId);
    if (!warehouse) return null;

    // If changing code, check for duplicates
    if (dto.code && dto.code !== warehouse.code) {
      const existing = await this.findByCode(dto.code, tenantId);
      if (existing) {
        throw new Error('A warehouse with this code already exists');
      }
    }

    // If setting as default, unset other defaults
    if (dto.isDefault && !warehouse.isDefault) {
      await this.warehouseRepository.update({ tenantId, isDefault: true }, { isDefault: false });
    }

    Object.assign(warehouse, {
      ...dto,
      updatedBy,
    });

    return this.warehouseRepository.save(warehouse);
  }

  async delete(id: string, tenantId: string): Promise<boolean> {
    const warehouse = await this.findOne(id, tenantId);
    if (!warehouse) return false;

    // Check if warehouse has locations
    const locations = await this.locationRepository.findOne({ where: { warehouseId: id } });
    if (locations) {
      throw new Error('Cannot delete warehouse with locations');
    }

    const result = await this.warehouseRepository.softDelete(id);
    return (result.affected ?? 0) > 0;
  }

  async getActiveWarehouses(tenantId: string): Promise<Warehouse[]> {
    return this.warehouseRepository.find({
      where: { tenantId, isActive: true },
      order: { isDefault: 'DESC', name: 'ASC' },
    });
  }

  // ==================== Locations ====================

  async findAllLocations(
    params: LocationSearchParams
  ): Promise<{ data: WarehouseLocation[]; total: number }> {
    const { warehouseId, search, locationType, parentId, isActive, limit = 100, offset = 0 } = params;

    const where: FindOptionsWhere<WarehouseLocation>[] = [];
    const baseWhere: FindOptionsWhere<WarehouseLocation> = { warehouseId };

    if (locationType) {
      baseWhere.locationType = locationType;
    }

    if (parentId !== undefined) {
      baseWhere.parentId = parentId || undefined;
    }

    if (isActive !== undefined) {
      baseWhere.isActive = isActive;
    }

    if (search) {
      where.push(
        { ...baseWhere, name: ILike(`%${search}%`) },
        { ...baseWhere, code: ILike(`%${search}%`) },
        { ...baseWhere, barcode: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const [data, total] = await this.locationRepository.findAndCount({
      where,
      take: limit,
      skip: offset,
      order: { hierarchyPath: 'ASC', code: 'ASC' },
    });

    return { data, total };
  }

  async findLocation(id: string): Promise<WarehouseLocation | null> {
    return this.locationRepository.findOne({ where: { id }, relations: ['warehouse'] });
  }

  async findLocationByCode(code: string, warehouseId: string): Promise<WarehouseLocation | null> {
    return this.locationRepository.findOne({ where: { code, warehouseId } });
  }

  async findLocationByBarcode(barcode: string): Promise<WarehouseLocation | null> {
    return this.locationRepository.findOne({ where: { barcode }, relations: ['warehouse'] });
  }

  async createLocation(dto: CreateWarehouseLocationDto): Promise<WarehouseLocation> {
    // Check for existing code in warehouse
    const existingCode = await this.findLocationByCode(dto.code, dto.warehouseId);
    if (existingCode) {
      throw new Error('A location with this code already exists in this warehouse');
    }

    // Calculate hierarchy if parent exists
    let hierarchyPath = `/${dto.code}`;
    let hierarchyLevel = 0;

    if (dto.parentId) {
      const parent = await this.findLocation(dto.parentId);
      if (parent) {
        hierarchyPath = `${parent.hierarchyPath}/${dto.code}`;
        hierarchyLevel = parent.hierarchyLevel + 1;
      }
    }

    const location = this.locationRepository.create({
      ...dto,
      hierarchyPath,
      hierarchyLevel,
    });

    return this.locationRepository.save(location);
  }

  async updateLocation(
    id: string,
    dto: UpdateWarehouseLocationDto
  ): Promise<WarehouseLocation | null> {
    const location = await this.findLocation(id);
    if (!location) return null;

    // If changing code, check for duplicates
    if (dto.code && dto.code !== location.code) {
      const existing = await this.findLocationByCode(dto.code, location.warehouseId);
      if (existing) {
        throw new Error('A location with this code already exists in this warehouse');
      }
    }

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

  async deleteLocation(id: string): Promise<boolean> {
    const location = await this.findLocation(id);
    if (!location) return false;

    // Check if location has children
    const children = await this.locationRepository.findOne({ where: { parentId: id } });
    if (children) {
      throw new Error('Cannot delete location with children');
    }

    const result = await this.locationRepository.softDelete(id);
    return (result.affected ?? 0) > 0;
  }

  async getLocationTree(warehouseId: string): Promise<WarehouseLocation[]> {
    return this.locationRepository.find({
      where: { warehouseId, isActive: true },
      order: { hierarchyPath: 'ASC' },
    });
  }

  async getPickableLocations(warehouseId: string): Promise<WarehouseLocation[]> {
    return this.locationRepository.find({
      where: { warehouseId, isActive: true, isPickable: true },
      order: { code: 'ASC' },
    });
  }

  async getReceivableLocations(warehouseId: string): Promise<WarehouseLocation[]> {
    return this.locationRepository.find({
      where: { warehouseId, isActive: true, isReceivable: true },
      order: { code: 'ASC' },
    });
  }
}
