import { env } from "../../config/env";
import { PaginationMeta } from "../../shared/types";
import {
  productRepository,
  ProductBaseRow,
  ProductCategoryRow,
  ProductSpecificationRow,
  ProductExistenceRow,
} from "./products.repository";
import { ProductListQuery } from "./products.schema";

export interface ProductDto {
  id: number;
  codigos: {
    interno: string | null;
    fabricante: string | null;
  };

  nombre: string | null;
  descripcion: string | null;
  clasificacionMG: string | null;

  estado: string | null;

  precio: {
    precioVentaSinIva: number;
    precioVentaConIva: number;
    ivaRate: number;
  };

  marca: {
    id: number;
    nombre: string | null;
    abreviatura: string | null;
    logo: string | null;
    estado: string | null;
  } | null;

  unidadNegocio: {
    id: number;
    nombre: string | null;
    abreviatura: string | null;
    logo: string | null;
    estado: string | null;
  } | null;

  unidadMedida: {
    id: number;
    codigoMH: string | null;
    nombre: string | null;
    abreviatura: string | null;
  } | null;

  tipoProducto: {
    id: number;
    nombre: string | null;
  } | null;

  paisOrigen: {
    id: number;
    nombre: string | null;
    abreviatura: string | null;
  } | null;

  categorias: Array<{
    id: number;
    nombre: string | null;
    abreviatura: string | null;
  }>;

  especificaciones: Array<{
    id: number;
    catalogoId: number | null;
    tipo: string | null;
    nombre: string | null;
    tipoMagnitud: string | null;
    valor: string | null;
    unidadMedida: {
      id: number;
      nombre: string | null;
      abreviatura: string | null;
    } | null;
  }>;

  existencias: Array<{
    sucursalId: number;
    nombreSucursal: string | null;
    existenciaTotal: number;
  }>;
}

function toNumber(value: string | number | null | undefined): number {
  if (value === null || value === undefined) return 0;

  return Number(value);
}

function roundMoney(value: number): number {
  return Math.round((value + Number.EPSILON) * 100) / 100;
}

function groupCategories(
  rows: ProductCategoryRow[],
): Map<number, ProductDto["categorias"]> {
  const map = new Map<number, ProductDto["categorias"]>();

  for (const row of rows) {
    const list = map.get(row.productoId) ?? [];

    list.push({
      id: row.inventarioCategoriaId,
      nombre: row.nombreCategoria,
      abreviatura: row.abreviatura,
    });

    map.set(row.productoId, list);
  }

  return map;
}

function groupSpecifications(
  rows: ProductSpecificationRow[],
): Map<number, ProductDto["especificaciones"]> {
  const map = new Map<number, ProductDto["especificaciones"]>();

  for (const row of rows) {
    const list = map.get(row.productoId) ?? [];

    list.push({
      id: row.prodEspecificacionId,
      catalogoId: row.catProdEspecificacionId,
      tipo: row.tipoEspecificacion,
      nombre: row.nombreProdEspecificacion,
      tipoMagnitud: row.tipoMagnitud,
      valor: row.valorEspecificacion,
      unidadMedida: row.unidadMedidaId
        ? {
            id: row.unidadMedidaId,
            nombre: row.nombreUnidadMedida,
            abreviatura: row.abreviaturaUnidadMedida,
          }
        : null,
    });

    map.set(row.productoId, list);
  }

  return map;
}

function toDto(
  row: ProductBaseRow,
  categoriesMap: Map<number, ProductDto["categorias"]>,
  specificationsMap: Map<number, ProductDto["especificaciones"]>,
  existencesMap: Map<number, ProductDto["existencias"]>,
): ProductDto {
  const precioVentaSinIva = toNumber(row.precioVenta);
  const precioVentaConIva = roundMoney(precioVentaSinIva * (1 + env.IVA_RATE));

  return {
    id: row.productoId,

    codigos: {
      interno: row.codInterno,
      fabricante: row.codFabricante,
    },

    nombre: row.nombreProducto,
    descripcion: row.descripcionProducto,
    clasificacionMG: row.clasificacionMG,

    estado: row.estadoProducto,

    precio: {
      precioVentaSinIva: roundMoney(precioVentaSinIva),
      precioVentaConIva,
      ivaRate: env.IVA_RATE,
    },

    marca: row.marcaId
      ? {
          id: row.marcaId,
          nombre: row.nombreMarca,
          abreviatura: row.abreviaturaMarca,
          logo: row.urlLogoMarca,
          estado: row.estadoMarca,
        }
      : null,

    unidadNegocio: row.udnId
      ? {
          id: row.udnId,
          nombre: row.nombreUDN,
          abreviatura: row.abreviaturaUDN,
          logo: row.urlLogoUDN,
          estado: row.estadoUDN,
        }
      : null,

    unidadMedida: row.unidadMedidaId
      ? {
          id: row.unidadMedidaId,
          codigoMH: row.codigoMHUnidadMedida,
          nombre: row.nombreUnidadMedida,
          abreviatura: row.abreviaturaUnidadMedida,
        }
      : null,

    tipoProducto: row.tipoProductoId
      ? {
          id: row.tipoProductoId,
          nombre: row.nombreTipoProducto,
        }
      : null,

    paisOrigen: row.paisIdOrigen
      ? {
          id: row.paisIdOrigen,
          nombre: row.pais,
          abreviatura: row.abreviaturaPais,
        }
      : null,

    categorias: categoriesMap.get(row.productoId) ?? [],

    especificaciones: specificationsMap.get(row.productoId) ?? [],
    existencias: existencesMap.get(row.productoId) ?? [],
  };
}

function groupExistences(
  rows: ProductExistenceRow[],
): Map<number, ProductDto["existencias"]> {
  const map = new Map<number, ProductDto["existencias"]>();

  for (const row of rows) {
    const list = map.get(row.productoId) ?? [];

    list.push({
      sucursalId: row.sucursalId,
      nombreSucursal: row.nombreSucursal,
      existenciaTotal: toNumber(row.existenciaTotal),
    });

    map.set(row.productoId, list);
  }

  return map;
}

async function buildProductsDto(rows: ProductBaseRow[]): Promise<ProductDto[]> {
  const productIds = rows.map((row) => row.productoId);

  const [categories, specifications,existences] = await Promise.all([
    productRepository.findCategoriesByProductIds(productIds),
    productRepository.findSpecificationsByProductIds(productIds),
    productRepository.findExistencesByProductIds(productIds),
  ]);

  const categoriesMap = groupCategories(categories);
  const specificationsMap = groupSpecifications(specifications);
  const existencesMap = groupExistences(existences);

  return rows.map((row) => toDto(row, categoriesMap, specificationsMap, existencesMap));
}

export const productService = {
  async getProducts(query: ProductListQuery): Promise<{
    products: ProductDto[];
    pagination: PaginationMeta;
  }> {
    const { rows, total } = await productRepository.findAll(query);
    const products = await buildProductsDto(rows);

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

  async getProductById(id: number): Promise<ProductDto | null> {
    const row = await productRepository.findById(id);

    if (!row) return null;

    const products = await buildProductsDto([row]);

    return products[0] ?? null;
  },

  async getProductByCode(code: string): Promise<ProductDto | null> {
    const row = await productRepository.findByCode(code);

    if (!row) return null;

    const products = await buildProductsDto([row]);

    return products[0] ?? null;
  },
};
