/**
 * AlmacenProyectoService - Project Warehouse Management Service
 *
 * Manages project-specific warehouse assignments, stock queries by project,
 * transfers between project warehouses, and project inventory summaries.
 *
 * @module Inventory
 */

import { Repository, FindOptionsWhere, DataSource } from 'typeorm';
import { AlmacenProyecto, WarehouseTypeConstruction } from '../entities/almacen-proyecto.entity';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreateAlmacenProyectoDto {
  warehouseId: string;
  fraccionamientoId: string;
  warehouseType?: WarehouseTypeConstruction;
  locationDescription?: string;
  responsibleId?: string;
}

export interface UpdateAlmacenProyectoDto {
  warehouseType?: WarehouseTypeConstruction;
  locationDescription?: string;
  responsibleId?: string;
  isActive?: boolean;
}

export interface AlmacenProyectoFilters {
  fraccionamientoId?: string;
  warehouseType?: WarehouseTypeConstruction;
  responsibleId?: string;
  isActive?: boolean;
}

export interface TransferRequest {
  sourceWarehouseId: string;
  destinationWarehouseId: string;
  productId: string;
  quantity: number;
  notes?: string;
}

export interface TransferResult {
  transferId: string;
  sourceWarehouseId: string;
  destinationWarehouseId: string;
  productId: string;
  quantity: number;
  transferredAt: Date;
  transferredById: string;
}

export interface StockByProject {
  productId: string;
  productName?: string;
  warehouseId: string;
  warehouseName?: string;
  quantity: number;
  reservedQuantity: number;
  availableQuantity: number;
}

export interface ProjectInventorySummary {
  fraccionamientoId: string;
  totalWarehouses: number;
  activeWarehouses: number;
  warehousesByType: { type: WarehouseTypeConstruction; count: number }[];
  totalProducts: number;
  totalStockValue: number;
}

export class AlmacenProyectoService {
  constructor(
    private readonly repository: Repository<AlmacenProyecto>,
    private readonly dataSource?: DataSource
  ) {}

  /**
   * Find project warehouses with filters and pagination
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: AlmacenProyectoFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<AlmacenProyecto>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.repository
      .createQueryBuilder('ap')
      .leftJoinAndSelect('ap.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('ap.responsible', 'responsible')
      .where('ap.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('ap.deleted_at IS NULL');

    if (filters.fraccionamientoId) {
      queryBuilder.andWhere('ap.fraccionamiento_id = :fraccionamientoId', {
        fraccionamientoId: filters.fraccionamientoId,
      });
    }

    if (filters.warehouseType) {
      queryBuilder.andWhere('ap.warehouse_type = :warehouseType', {
        warehouseType: filters.warehouseType,
      });
    }

    if (filters.responsibleId) {
      queryBuilder.andWhere('ap.responsible_id = :responsibleId', {
        responsibleId: filters.responsibleId,
      });
    }

    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('ap.is_active = :isActive', {
        isActive: filters.isActive,
      });
    }

    queryBuilder
      .orderBy('ap.created_at', 'DESC')
      .skip(skip)
      .take(limit);

    const [data, total] = await queryBuilder.getManyAndCount();

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

  /**
   * Find a project warehouse by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<AlmacenProyecto | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<AlmacenProyecto>,
      relations: ['fraccionamiento', 'responsible', 'createdBy'],
    });
  }

  /**
   * Find a project warehouse by warehouse ID
   */
  async findByWarehouseId(ctx: ServiceContext, warehouseId: string): Promise<AlmacenProyecto | null> {
    return this.repository.findOne({
      where: {
        warehouseId,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<AlmacenProyecto>,
      relations: ['fraccionamiento', 'responsible'],
    });
  }

  /**
   * Find all warehouses for a specific project (fraccionamiento)
   */
  async findByProject(ctx: ServiceContext, fraccionamientoId: string): Promise<AlmacenProyecto[]> {
    return this.repository.find({
      where: {
        fraccionamientoId,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<AlmacenProyecto>,
      relations: ['responsible'],
      order: { createdAt: 'ASC' },
    });
  }

  /**
   * Find active warehouses by type for a project
   */
  async findByProjectAndType(
    ctx: ServiceContext,
    fraccionamientoId: string,
    warehouseType: WarehouseTypeConstruction
  ): Promise<AlmacenProyecto[]> {
    return this.repository.find({
      where: {
        fraccionamientoId,
        warehouseType,
        isActive: true,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<AlmacenProyecto>,
      relations: ['responsible'],
    });
  }

  /**
   * Create a new project warehouse assignment
   */
  async create(ctx: ServiceContext, dto: CreateAlmacenProyectoDto): Promise<AlmacenProyecto> {
    // Check if warehouse is already assigned to a project
    const existing = await this.findByWarehouseId(ctx, dto.warehouseId);
    if (existing) {
      throw new Error('Warehouse is already assigned to a project');
    }

    const almacenProyecto = this.repository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      warehouseId: dto.warehouseId,
      fraccionamientoId: dto.fraccionamientoId,
      warehouseType: dto.warehouseType || 'obra',
      locationDescription: dto.locationDescription,
      responsibleId: dto.responsibleId,
      isActive: true,
    });

    return this.repository.save(almacenProyecto);
  }

  /**
   * Update a project warehouse
   */
  async update(
    ctx: ServiceContext,
    id: string,
    dto: UpdateAlmacenProyectoDto
  ): Promise<AlmacenProyecto | null> {
    const almacenProyecto = await this.findById(ctx, id);
    if (!almacenProyecto) {
      return null;
    }

    if (dto.warehouseType !== undefined) {
      almacenProyecto.warehouseType = dto.warehouseType;
    }
    if (dto.locationDescription !== undefined) {
      almacenProyecto.locationDescription = dto.locationDescription;
    }
    if (dto.responsibleId !== undefined) {
      almacenProyecto.responsibleId = dto.responsibleId;
    }
    if (dto.isActive !== undefined) {
      almacenProyecto.isActive = dto.isActive;
    }

    if (ctx.userId) {
      almacenProyecto.updatedById = ctx.userId;
    }

    return this.repository.save(almacenProyecto);
  }

  /**
   * Activate a project warehouse
   */
  async activate(ctx: ServiceContext, id: string): Promise<AlmacenProyecto | null> {
    return this.update(ctx, id, { isActive: true });
  }

  /**
   * Deactivate a project warehouse
   */
  async deactivate(ctx: ServiceContext, id: string): Promise<AlmacenProyecto | null> {
    return this.update(ctx, id, { isActive: false });
  }

  /**
   * Get stock by project (all warehouses)
   * Note: This queries against ERP Core inventory tables
   */
  async getStockByProject(
    ctx: ServiceContext,
    fraccionamientoId: string
  ): Promise<StockByProject[]> {
    if (!this.dataSource) {
      throw new Error('DataSource is required for stock queries');
    }

    // Get all warehouse IDs for this project
    const projectWarehouses = await this.findByProject(ctx, fraccionamientoId);
    if (projectWarehouses.length === 0) {
      return [];
    }

    const warehouseIds = projectWarehouses.map((w) => w.warehouseId);

    // Query stock from ERP Core inventory.stock table
    const stockQuery = `
      SELECT
        s.product_id as "productId",
        p.name as "productName",
        s.warehouse_id as "warehouseId",
        w.name as "warehouseName",
        COALESCE(s.quantity, 0) as quantity,
        COALESCE(s.reserved_quantity, 0) as "reservedQuantity",
        COALESCE(s.quantity, 0) - COALESCE(s.reserved_quantity, 0) as "availableQuantity"
      FROM inventory.stock s
      LEFT JOIN inventory.products p ON s.product_id = p.id
      LEFT JOIN inventory.warehouses w ON s.warehouse_id = w.id
      WHERE s.tenant_id = $1
        AND s.warehouse_id = ANY($2::uuid[])
      ORDER BY p.name, w.name
    `;

    try {
      const result = await this.dataSource.query(stockQuery, [ctx.tenantId, warehouseIds]);
      return result.map((row: Record<string, unknown>) => ({
        productId: row.productId as string,
        productName: row.productName as string | undefined,
        warehouseId: row.warehouseId as string,
        warehouseName: row.warehouseName as string | undefined,
        quantity: parseFloat(String(row.quantity) || '0'),
        reservedQuantity: parseFloat(String(row.reservedQuantity) || '0'),
        availableQuantity: parseFloat(String(row.availableQuantity) || '0'),
      }));
    } catch {
      // If inventory.stock table doesn't exist (ERP Core not fully set up),
      // return empty array
      return [];
    }
  }

  /**
   * Get stock for a specific product across all project warehouses
   */
  async getProductStockByProject(
    ctx: ServiceContext,
    fraccionamientoId: string,
    productId: string
  ): Promise<StockByProject[]> {
    const allStock = await this.getStockByProject(ctx, fraccionamientoId);
    return allStock.filter((s) => s.productId === productId);
  }

  /**
   * Transfer stock between project warehouses
   * Note: This creates movement records in ERP Core inventory
   */
  async transferBetweenWarehouses(
    ctx: ServiceContext,
    request: TransferRequest
  ): Promise<TransferResult> {
    if (!ctx.userId) {
      throw new Error('User ID is required for transfer operations');
    }

    if (!this.dataSource) {
      throw new Error('DataSource is required for transfer operations');
    }

    // Validate source warehouse belongs to a project
    const sourceWarehouse = await this.findByWarehouseId(ctx, request.sourceWarehouseId);
    if (!sourceWarehouse) {
      throw new Error('Source warehouse is not assigned to any project');
    }

    // Validate destination warehouse belongs to a project
    const destWarehouse = await this.findByWarehouseId(ctx, request.destinationWarehouseId);
    if (!destWarehouse) {
      throw new Error('Destination warehouse is not assigned to any project');
    }

    // Validate quantity
    if (request.quantity <= 0) {
      throw new Error('Transfer quantity must be positive');
    }

    // Check available stock in source warehouse
    const sourceStock = await this.getWarehouseProductStock(
      ctx,
      request.sourceWarehouseId,
      request.productId
    );
    if (sourceStock.availableQuantity < request.quantity) {
      throw new Error(
        `Insufficient stock. Available: ${sourceStock.availableQuantity}, Requested: ${request.quantity}`
      );
    }

    // Create transfer movement in ERP Core
    const transferId = await this.createTransferMovement(ctx, request);

    return {
      transferId,
      sourceWarehouseId: request.sourceWarehouseId,
      destinationWarehouseId: request.destinationWarehouseId,
      productId: request.productId,
      quantity: request.quantity,
      transferredAt: new Date(),
      transferredById: ctx.userId,
    };
  }

  /**
   * Get stock for a specific product in a warehouse
   */
  private async getWarehouseProductStock(
    ctx: ServiceContext,
    warehouseId: string,
    productId: string
  ): Promise<{ quantity: number; reservedQuantity: number; availableQuantity: number }> {
    if (!this.dataSource) {
      return { quantity: 0, reservedQuantity: 0, availableQuantity: 0 };
    }

    const query = `
      SELECT
        COALESCE(quantity, 0) as quantity,
        COALESCE(reserved_quantity, 0) as "reservedQuantity"
      FROM inventory.stock
      WHERE tenant_id = $1
        AND warehouse_id = $2
        AND product_id = $3
    `;

    try {
      const result = await this.dataSource.query(query, [ctx.tenantId, warehouseId, productId]);
      if (result.length === 0) {
        return { quantity: 0, reservedQuantity: 0, availableQuantity: 0 };
      }
      const row = result[0];
      const quantity = parseFloat(row.quantity || '0');
      const reservedQuantity = parseFloat(row.reservedQuantity || '0');
      return {
        quantity,
        reservedQuantity,
        availableQuantity: quantity - reservedQuantity,
      };
    } catch {
      return { quantity: 0, reservedQuantity: 0, availableQuantity: 0 };
    }
  }

  /**
   * Create a transfer movement between warehouses
   */
  private async createTransferMovement(
    ctx: ServiceContext,
    request: TransferRequest
  ): Promise<string> {
    if (!this.dataSource) {
      throw new Error('DataSource is required');
    }

    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // Generate transfer ID
      const transferId = this.generateUUID();
      const now = new Date();

      // Create outbound movement from source
      await queryRunner.query(
        `
        INSERT INTO inventory.stock_moves (
          id, tenant_id, product_id, warehouse_id,
          quantity, movement_type, reference_type, reference_id,
          notes, created_at, created_by
        ) VALUES ($1, $2, $3, $4, $5, 'out', 'transfer', $6, $7, $8, $9)
      `,
        [
          this.generateUUID(),
          ctx.tenantId,
          request.productId,
          request.sourceWarehouseId,
          -request.quantity,
          transferId,
          request.notes || 'Transfer between project warehouses',
          now,
          ctx.userId,
        ]
      );

      // Create inbound movement to destination
      await queryRunner.query(
        `
        INSERT INTO inventory.stock_moves (
          id, tenant_id, product_id, warehouse_id,
          quantity, movement_type, reference_type, reference_id,
          notes, created_at, created_by
        ) VALUES ($1, $2, $3, $4, $5, 'in', 'transfer', $6, $7, $8, $9)
      `,
        [
          this.generateUUID(),
          ctx.tenantId,
          request.productId,
          request.destinationWarehouseId,
          request.quantity,
          transferId,
          request.notes || 'Transfer between project warehouses',
          now,
          ctx.userId,
        ]
      );

      // Update stock in source warehouse (decrease)
      await queryRunner.query(
        `
        UPDATE inventory.stock
        SET quantity = quantity - $1, updated_at = $2
        WHERE tenant_id = $3 AND warehouse_id = $4 AND product_id = $5
      `,
        [request.quantity, now, ctx.tenantId, request.sourceWarehouseId, request.productId]
      );

      // Update or insert stock in destination warehouse (increase)
      await queryRunner.query(
        `
        INSERT INTO inventory.stock (id, tenant_id, warehouse_id, product_id, quantity, created_at)
        VALUES ($1, $2, $3, $4, $5, $6)
        ON CONFLICT (tenant_id, warehouse_id, product_id)
        DO UPDATE SET quantity = inventory.stock.quantity + $5, updated_at = $6
      `,
        [
          this.generateUUID(),
          ctx.tenantId,
          request.destinationWarehouseId,
          request.productId,
          request.quantity,
          now,
        ]
      );

      await queryRunner.commitTransaction();
      return transferId;
    } catch (error) {
      await queryRunner.rollbackTransaction();
      throw error;
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * Get project inventory summary
   */
  async getProjectInventorySummary(
    ctx: ServiceContext,
    fraccionamientoId: string
  ): Promise<ProjectInventorySummary> {
    // Get warehouses for the project
    const warehouses = await this.findByProject(ctx, fraccionamientoId);
    const activeWarehouses = warehouses.filter((w) => w.isActive);

    // Count by type
    const typeCount: Record<WarehouseTypeConstruction, number> = {
      central: 0,
      obra: 0,
      temporal: 0,
      transito: 0,
    };

    for (const wh of activeWarehouses) {
      typeCount[wh.warehouseType]++;
    }

    const warehousesByType = Object.entries(typeCount)
      .filter(([, count]) => count > 0)
      .map(([type, count]) => ({
        type: type as WarehouseTypeConstruction,
        count,
      }));

    // Get stock summary if dataSource available
    let totalProducts = 0;
    let totalStockValue = 0;

    if (this.dataSource && activeWarehouses.length > 0) {
      const warehouseIds = activeWarehouses.map((w) => w.warehouseId);

      try {
        const stockSummary = await this.dataSource.query(
          `
          SELECT
            COUNT(DISTINCT s.product_id) as "totalProducts",
            COALESCE(SUM(s.quantity * COALESCE(p.cost, 0)), 0) as "totalValue"
          FROM inventory.stock s
          LEFT JOIN inventory.products p ON s.product_id = p.id
          WHERE s.tenant_id = $1
            AND s.warehouse_id = ANY($2::uuid[])
            AND s.quantity > 0
        `,
          [ctx.tenantId, warehouseIds]
        );

        if (stockSummary.length > 0) {
          totalProducts = parseInt(stockSummary[0].totalProducts || '0', 10);
          totalStockValue = parseFloat(stockSummary[0].totalValue || '0');
        }
      } catch {
        // ERP Core tables may not exist
      }
    }

    return {
      fraccionamientoId,
      totalWarehouses: warehouses.length,
      activeWarehouses: activeWarehouses.length,
      warehousesByType,
      totalProducts,
      totalStockValue,
    };
  }

  /**
   * Soft delete a project warehouse assignment
   */
  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const almacenProyecto = await this.findById(ctx, id);
    if (!almacenProyecto) {
      return false;
    }

    await this.repository.update(
      { id, tenantId: ctx.tenantId } as unknown as FindOptionsWhere<AlmacenProyecto>,
      { deletedAt: new Date(), deletedById: ctx.userId }
    );

    return true;
  }

  /**
   * Generate a UUID v4
   */
  private generateUUID(): string {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
      const r = (Math.random() * 16) | 0;
      const v = c === 'x' ? r : (r & 0x3) | 0x8;
      return v.toString(16);
    });
  }
}
