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

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

export interface CreateLocationDto {
  warehouseId?: string;
  name: string;
  locationType: LocationType;
  parentId?: string;
  isScrapLocation?: boolean;
  isReturnLocation?: boolean;
}

export interface UpdateLocationDto {
  name?: string;
  parentId?: string | null;
  isScrapLocation?: boolean;
  isReturnLocation?: boolean;
  active?: boolean;
}

export interface LocationFilters {
  warehouseId?: string;
  locationType?: LocationType;
  active?: boolean;
  page?: number;
  limit?: number;
}

export interface LocationWithRelations extends Location {
  warehouseName?: string;
  parentName?: string;
}

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

class LocationsService {
  private locationRepository: Repository<Location>;
  private stockQuantRepository: Repository<StockQuant>;

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

  /**
   * Get all locations with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: LocationFilters = {}
  ): Promise<{ data: LocationWithRelations[]; total: number }> {
    try {
      const { warehouseId, locationType, active, page = 1, limit = 50 } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.locationRepository
        .createQueryBuilder('location')
        .leftJoinAndSelect('location.warehouse', 'warehouse')
        .leftJoinAndSelect('location.parent', 'parent')
        .where('location.tenantId = :tenantId', { tenantId });

      // Filter by warehouse
      if (warehouseId) {
        queryBuilder.andWhere('location.warehouseId = :warehouseId', { warehouseId });
      }

      // Filter by location type
      if (locationType) {
        queryBuilder.andWhere('location.locationType = :locationType', { locationType });
      }

      // Filter by active status
      if (active !== undefined) {
        queryBuilder.andWhere('location.active = :active', { active });
      }

      // Get total count
      const total = await queryBuilder.getCount();

      // Get paginated results
      const locations = await queryBuilder
        .orderBy('location.completeName', 'ASC')
        .skip(skip)
        .take(limit)
        .getMany();

      // Map to include relation names for backward compatibility
      const data: LocationWithRelations[] = locations.map((loc) => ({
        ...loc,
        warehouseName: loc.warehouse?.name,
        parentName: loc.parent?.name,
      }));

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

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

  /**
   * Get location by ID
   */
  async findById(id: string, tenantId: string): Promise<LocationWithRelations> {
    try {
      const location = await this.locationRepository
        .createQueryBuilder('location')
        .leftJoinAndSelect('location.warehouse', 'warehouse')
        .leftJoinAndSelect('location.parent', 'parent')
        .where('location.id = :id', { id })
        .andWhere('location.tenantId = :tenantId', { tenantId })
        .getOne();

      if (!location) {
        throw new NotFoundError('Ubicacion no encontrada');
      }

      return {
        ...location,
        warehouseName: location.warehouse?.name,
        parentName: location.parent?.name,
      };
    } catch (error) {
      logger.error('Error finding location', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Create a new location
   */
  async create(dto: CreateLocationDto, tenantId: string, userId: string): Promise<Location> {
    try {
      // Validate parent location if specified
      if (dto.parentId) {
        const parent = await this.locationRepository.findOne({
          where: {
            id: dto.parentId,
            tenantId,
          },
        });

        if (!parent) {
          throw new NotFoundError('Ubicacion padre no encontrada');
        }
      }

      // Create location
      const location = this.locationRepository.create({
        tenantId,
        warehouseId: dto.warehouseId || null,
        name: dto.name,
        locationType: dto.locationType,
        parentId: dto.parentId || null,
        isScrapLocation: dto.isScrapLocation || false,
        isReturnLocation: dto.isReturnLocation || false,
        createdBy: userId,
      });

      await this.locationRepository.save(location);

      logger.info('Location created', {
        locationId: location.id,
        tenantId,
        name: location.name,
        createdBy: userId,
      });

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

  /**
   * Update a location
   */
  async update(id: string, dto: UpdateLocationDto, tenantId: string, userId: string): Promise<Location> {
    try {
      const existing = await this.findById(id, tenantId);

      // Validate parent (prevent self-reference)
      if (dto.parentId) {
        if (dto.parentId === id) {
          throw new ConflictError('Una ubicacion no puede ser su propia ubicacion padre');
        }

        // Validate parent exists
        const parent = await this.locationRepository.findOne({
          where: {
            id: dto.parentId,
            tenantId,
          },
        });

        if (!parent) {
          throw new NotFoundError('Ubicacion padre no encontrada');
        }
      }

      // Update allowed fields
      if (dto.name !== undefined) existing.name = dto.name;
      if (dto.parentId !== undefined) existing.parentId = dto.parentId;
      if (dto.isScrapLocation !== undefined) existing.isScrapLocation = dto.isScrapLocation;
      if (dto.isReturnLocation !== undefined) existing.isReturnLocation = dto.isReturnLocation;
      if (dto.active !== undefined) existing.active = dto.active;

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

      await this.locationRepository.save(existing);

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

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

  /**
   * Get stock for a location
   */
  async getStock(locationId: string, tenantId: string): Promise<any[]> {
    try {
      await this.findById(locationId, tenantId);

      const stockQuants = await this.stockQuantRepository
        .createQueryBuilder('sq')
        .leftJoinAndSelect('sq.product', 'product')
        .where('sq.locationId = :locationId', { locationId })
        .andWhere('sq.quantity > 0')
        .orderBy('product.name', 'ASC')
        .getMany();

      // Map to include product details
      // Note: UOM name would need a join to core schema - for now we return productUomId
      return stockQuants.map((sq) => ({
        id: sq.id,
        productId: sq.productId,
        productName: sq.product?.name,
        productCode: sq.product?.code,
        uomId: sq.product?.uomId,
        locationId: sq.locationId,
        lotId: sq.lotId,
        quantity: sq.quantity,
        reservedQuantity: sq.reservedQuantity,
        createdAt: sq.createdAt,
        updatedAt: sq.updatedAt,
      }));
    } catch (error) {
      logger.error('Error getting location stock', {
        error: (error as Error).message,
        locationId,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get children locations
   */
  async getChildren(locationId: string, tenantId: string): Promise<Location[]> {
    try {
      await this.findById(locationId, tenantId);

      const children = await this.locationRepository
        .createQueryBuilder('location')
        .where('location.parentId = :locationId', { locationId })
        .andWhere('location.tenantId = :tenantId', { tenantId })
        .orderBy('location.name', 'ASC')
        .getMany();

      return children;
    } catch (error) {
      logger.error('Error getting location children', {
        error: (error as Error).message,
        locationId,
        tenantId,
      });
      throw error;
    }
  }
}

// ===== Export Singleton Instance =====

export const locationsService = new LocationsService();

// Re-export Location and LocationType for backward compatibility
export { Location, LocationType };
