
import {
  pgGetChecksReceived,
  pgGetDefaultCurrency,
  pgGetExchangeRate,
  pgGetInventoryMovements,
  pgGetItemGroups,
  pgGetMaterials,
  pgGetSettings,
  pgGetWarehouses,
} from '@/lib/postgres/data-access';
import type { PurchaseReferenceDataPort, TaxCalcItemGroup } from '../application/purchase-reference-data.port';

export class DefaultPurchaseReferenceDataPort implements PurchaseReferenceDataPort {
  private materialsCache: Array<Record<string, any>> = [];

  async getSettings(): Promise<Record<string, unknown>> {
    
    return (await pgGetSettings()) as Record<string, unknown>;
  }

  async getDefaultBaseCurrencyCode(): Promise<string> {
    
    return String((await pgGetDefaultCurrency())?.code || 'USD');
  }

  async getIncomingChecks(): Promise<Array<Record<string, any>>> {
    
    const result = await pgGetChecksReceived({ page: 1, pageSize: 5000 });
    return (result.items || []) as Array<Record<string, any>>;
  }

  async convertCurrency(amount: number, fromCode: string, toCode: string): Promise<number> {
    const normalizedAmount = Number(amount || 0);
    const fromCurrency = String(fromCode || '').trim();
    const toCurrency = String(toCode || '').trim();

    if (!fromCurrency || !toCurrency || fromCurrency === toCurrency) {
      return Math.round(normalizedAmount * 100) / 100;
    }

    
    const rate = await pgGetExchangeRate(fromCurrency, toCurrency);
    return Math.round(normalizedAmount * Number(rate || 1) * 100) / 100;
  }

  assertNotLockedByPeriod(dateIso: string, periodCloseDate?: string, inventoryCloseDate?: string): void {
    const movementDate = new Date(dateIso);
    if (periodCloseDate && movementDate <= new Date(periodCloseDate)) {
      throw new Error('PERIOD_LOCKED');
    }
    if (inventoryCloseDate && movementDate <= new Date(inventoryCloseDate)) {
      throw new Error('INVENTORY_LOCKED');
    }
  }

  async getItemGroupsForTaxCalc(): Promise<TaxCalcItemGroup[]> {
    
    const [groupsResult, materialsResult] = await Promise.all([
    pgGetItemGroups({ page: 1, pageSize: 5000 }),
    pgGetMaterials({ page: 1, pageSize: 5000 }),
    ]);
    this.materialsCache = (materialsResult.items || []) as Array<Record<string, any>>;
    return (groupsResult.items || []) as TaxCalcItemGroup[];
  }

  getTaxRateForMaterial(materialId: string, itemGroupMap: Map<string, any>, defaultTaxRate: number): number {
    const materials = this.materialsCache || [];
    const material = materials.find((entry: any) => entry.id === materialId);

    if (material?.taxRate !== undefined && Number(material.taxRate) > 0) {
      return Number(material.taxRate);
    }

    if (material?.groupId) {
      const group = itemGroupMap.get(material.groupId);
      if (group?.taxRate !== undefined && Number(group.taxRate) > 0) {
        return Number(group.taxRate);
      }
    }

    return defaultTaxRate;
  }

  async getStockLocationsByMaterial(materialIds: string[]): Promise<any[]> {
    const normalizedMaterialIds = Array.from(new Set((materialIds || []).map((id) => String(id || '').trim()).filter(Boolean)));
    if (normalizedMaterialIds.length === 0) return [];

    
    const [movementsResult, warehousesResult] = await Promise.all([
    pgGetInventoryMovements({ page: 1, pageSize: 10000 }),
    pgGetWarehouses({ page: 1, pageSize: 5000 }),
    ]);

    const allowedMaterialIds = new Set(normalizedMaterialIds);
    const warehouses = (warehousesResult.items || []) as Array<Record<string, any>>;
    const warehouseNameById = new Map(warehouses.map((warehouse) => [String(warehouse.id || ''), String(warehouse.name || warehouse.id || '')]));
    const shelfNameByWarehouseAndId = new Map<string, string>();
    warehouses.forEach((warehouse) => {
    (Array.isArray(warehouse.shelves) ? warehouse.shelves : []).forEach((shelf: any) => {
      shelfNameByWarehouseAndId.set(
        `${String(warehouse.id || '')}||${String(shelf.id || '')}`,
        String(shelf.name || shelf.id || '')
      );
    });
    });

    const balanceMap = ((movementsResult.items || []) as Array<Record<string, any>>).reduce<Map<string, number>>((acc, movement) => {
    const materialId = String(movement.materialId || '').trim();
    const warehouseId = String(movement.warehouseId || '').trim();
    const shelfId = String(movement.shelfId || '').trim();
    if (!allowedMaterialIds.has(materialId) || !warehouseId || !shelfId) return acc;

    const netQuantity = Number(movement.quantityIn || 0) - Number(movement.quantityOut || 0);
    if (!Number.isFinite(netQuantity) || netQuantity === 0) return acc;

    const key = `${materialId}||${warehouseId}||${shelfId}`;
    acc.set(key, (acc.get(key) || 0) + netQuantity);
    return acc;
    }, new Map<string, number>());

    const grouped = Array.from(balanceMap.entries()).reduce<Record<string, any[]>>((acc, [key, quantity]) => {
    if (quantity <= 0) return acc;
    const [materialId, warehouseId, shelfId] = key.split('||');
    if (!materialId || !warehouseId || !shelfId) return acc;

    const locations = acc[materialId] || [];
    locations.push({
      materialId,
      warehouseId,
      warehouseName: warehouseNameById.get(warehouseId) || warehouseId,
      shelfId,
      shelfName: shelfNameByWarehouseAndId.get(`${warehouseId}||${shelfId}`) || shelfId,
      quantity,
    });
    acc[materialId] = locations;
    return acc;
    }, {});

    return normalizedMaterialIds.map((materialId) => ({
    materialId,
    locations: grouped[materialId] || [],
    }));
  }

  nextPrefixedId(prefix: string): string {
    const normalizedPrefix = String(prefix || 'ref').trim() || 'ref';
    return `${normalizedPrefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  }
}

export function createDefaultPurchaseReferenceDataPort(): PurchaseReferenceDataPort {
  return new DefaultPurchaseReferenceDataPort();
}
