import { query, queryOne } from '../../config/database.js';
import { NotFoundError, ConflictError } from '../../shared/errors/index.js';

export type LocationType = 'internal' | 'supplier' | 'customer' | 'inventory' | 'production' | 'transit';

export interface Location {
  id: string;
  tenant_id: string;
  warehouse_id?: string;
  warehouse_name?: string;
  name: string;
  complete_name?: string;
  location_type: LocationType;
  parent_id?: string;
  parent_name?: string;
  is_scrap_location: boolean;
  is_return_location: boolean;
  active: boolean;
  created_at: Date;
}

export interface CreateLocationDto {
  warehouse_id?: string;
  name: string;
  location_type: LocationType;
  parent_id?: string;
  is_scrap_location?: boolean;
  is_return_location?: boolean;
}

export interface UpdateLocationDto {
  name?: string;
  parent_id?: string | null;
  is_scrap_location?: boolean;
  is_return_location?: boolean;
  active?: boolean;
}

export interface LocationFilters {
  warehouse_id?: string;
  location_type?: LocationType;
  active?: boolean;
  page?: number;
  limit?: number;
}

class LocationsService {
  async findAll(tenantId: string, filters: LocationFilters = {}): Promise<{ data: Location[]; total: number }> {
    const { warehouse_id, location_type, active, page = 1, limit = 50 } = filters;
    const offset = (page - 1) * limit;

    let whereClause = 'WHERE l.tenant_id = $1';
    const params: any[] = [tenantId];
    let paramIndex = 2;

    if (warehouse_id) {
      whereClause += ` AND l.warehouse_id = $${paramIndex++}`;
      params.push(warehouse_id);
    }

    if (location_type) {
      whereClause += ` AND l.location_type = $${paramIndex++}`;
      params.push(location_type);
    }

    if (active !== undefined) {
      whereClause += ` AND l.active = $${paramIndex++}`;
      params.push(active);
    }

    const countResult = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM inventory.locations l ${whereClause}`,
      params
    );

    params.push(limit, offset);
    const data = await query<Location>(
      `SELECT l.*,
              w.name as warehouse_name,
              lp.name as parent_name
       FROM inventory.locations l
       LEFT JOIN inventory.warehouses w ON l.warehouse_id = w.id
       LEFT JOIN inventory.locations lp ON l.parent_id = lp.id
       ${whereClause}
       ORDER BY l.complete_name
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

    return {
      data,
      total: parseInt(countResult?.count || '0', 10),
    };
  }

  async findById(id: string, tenantId: string): Promise<Location> {
    const location = await queryOne<Location>(
      `SELECT l.*,
              w.name as warehouse_name,
              lp.name as parent_name
       FROM inventory.locations l
       LEFT JOIN inventory.warehouses w ON l.warehouse_id = w.id
       LEFT JOIN inventory.locations lp ON l.parent_id = lp.id
       WHERE l.id = $1 AND l.tenant_id = $2`,
      [id, tenantId]
    );

    if (!location) {
      throw new NotFoundError('Ubicación no encontrada');
    }

    return location;
  }

  async create(dto: CreateLocationDto, tenantId: string, userId: string): Promise<Location> {
    // Validate parent location if specified
    if (dto.parent_id) {
      const parent = await queryOne<Location>(
        `SELECT id FROM inventory.locations WHERE id = $1 AND tenant_id = $2`,
        [dto.parent_id, tenantId]
      );
      if (!parent) {
        throw new NotFoundError('Ubicación padre no encontrada');
      }
    }

    const location = await queryOne<Location>(
      `INSERT INTO inventory.locations (tenant_id, warehouse_id, name, location_type, parent_id, is_scrap_location, is_return_location, created_by)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
       RETURNING *`,
      [
        tenantId,
        dto.warehouse_id,
        dto.name,
        dto.location_type,
        dto.parent_id,
        dto.is_scrap_location || false,
        dto.is_return_location || false,
        userId,
      ]
    );

    return location!;
  }

  async update(id: string, dto: UpdateLocationDto, tenantId: string, userId: string): Promise<Location> {
    await this.findById(id, tenantId);

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

    const updateFields: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    if (dto.name !== undefined) {
      updateFields.push(`name = $${paramIndex++}`);
      values.push(dto.name);
    }
    if (dto.parent_id !== undefined) {
      updateFields.push(`parent_id = $${paramIndex++}`);
      values.push(dto.parent_id);
    }
    if (dto.is_scrap_location !== undefined) {
      updateFields.push(`is_scrap_location = $${paramIndex++}`);
      values.push(dto.is_scrap_location);
    }
    if (dto.is_return_location !== undefined) {
      updateFields.push(`is_return_location = $${paramIndex++}`);
      values.push(dto.is_return_location);
    }
    if (dto.active !== undefined) {
      updateFields.push(`active = $${paramIndex++}`);
      values.push(dto.active);
    }

    updateFields.push(`updated_by = $${paramIndex++}`);
    values.push(userId);
    updateFields.push(`updated_at = CURRENT_TIMESTAMP`);

    values.push(id, tenantId);

    const location = await queryOne<Location>(
      `UPDATE inventory.locations SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex}
       RETURNING *`,
      values
    );

    return location!;
  }

  async getStock(locationId: string, tenantId: string): Promise<any[]> {
    await this.findById(locationId, tenantId);

    return query(
      `SELECT sq.*, p.name as product_name, p.code as product_code, u.name as uom_name
       FROM inventory.stock_quants sq
       INNER JOIN inventory.products p ON sq.product_id = p.id
       LEFT JOIN core.uom u ON p.uom_id = u.id
       WHERE sq.location_id = $1 AND sq.quantity > 0
       ORDER BY p.name`,
      [locationId]
    );
  }
}

export const locationsService = new LocationsService();
