import { Knex } from "knex";
import { db } from "../../config/database";
import { ProductListQuery } from "./products.schema";

export interface ProductBaseRow {
  productoId: number;

  codFabricante: string | null;
  codInterno: string | null;

  nombreProducto: string | null;
  descripcionProducto: string | null;
  clasificacionMG: string | null;

  marcaId: number | null;
  nombreMarca: string | null;
  abreviaturaMarca: string | null;
  urlLogoMarca: string | null;
  estadoMarca: string | null;

  unidadMedidaId: number | null;
  nombreUnidadMedida: string | null;
  abreviaturaUnidadMedida: string | null;
  codigoMHUnidadMedida: string | null;

  tipoProductoId: number | null;
  nombreTipoProducto: string | null;

  paisIdOrigen: number | null;
  pais: string | null;
  abreviaturaPais: string | null;

  udnId: number | null;
  nombreUDN: string | null;
  abreviaturaUDN: string | null;
  urlLogoUDN: string | null;
  estadoUDN: string | null;

  estadoProducto: string | null;

  productoPrecioId: number | null;
  precioVenta: string | number | null;
}

export interface ProductCategoryRow {
  productoId: number;
  inventarioCategoriaId: number;
  abreviatura: string | null;
  nombreCategoria: string | null;
}

export interface ProductSpecificationRow {
  productoId: number;
  prodEspecificacionId: number;
  catProdEspecificacionId: number | null;
  tipoEspecificacion: string | null;
  nombreProdEspecificacion: string | null;
  tipoMagnitud: string | null;
  valorEspecificacion: string | null;
  unidadMedidaId: number | null;
  nombreUnidadMedida: string | null;
  abreviaturaUnidadMedida: string | null;
}
export interface ProductExistenceRow {
  productoId: number;
  sucursalId: number;
  nombreSucursal: string | null;
  existenciaTotal: string | number | null;
}

function latestPriceSubquery() {
  return db("prod_productos_precios as pp1")
    .select("pp1.*")
    .where("pp1.flgDelete", 0)
    .where("pp1.estadoPrecio", "Activo")
    .whereRaw(
      `
      pp1.productoPrecioId = (
        SELECT MAX(pp2.productoPrecioId)
        FROM prod_productos_precios pp2
        WHERE pp2.productoId = pp1.productoId
          AND pp2.flgDelete = 0
          AND pp2.estadoPrecio = 'Activo'
      )
    `,
    )
    .as("pp");
}
function applyProductFilters(query: any, params: ProductListQuery): void {
  query.where("p.flgDelete", 0);
  query.where("p.estadoProducto", "Activo");

  // Búsqueda general: nombre, código interno o código fabricante
  if (params.search) {
    const search = `%${params.search}%`;

    query.where(function (this: Knex.QueryBuilder) {
      this.where("p.nombreProducto", "like", search)
        .orWhere("p.codInterno", "like", search)
        .orWhere("p.codFabricante", "like", search);
    });
  }

  // Filtro específico por nombre
  if (params.nombre) {
    query.where("p.nombreProducto", "like", `%${params.nombre}%`);
  }

  // Filtro específico por código interno
  if (params.codInterno) {
    query.where("p.codInterno", "like", `%${params.codInterno}%`);
  }

  // Filtro específico por código fabricante
  if (params.codFabricante) {
    query.where("p.codFabricante", "like", `%${params.codFabricante}%`);
  }

  // Filtro por marca
  if (params.marcaId) {
    query.where("p.marcaId", params.marcaId);
  }

  // Filtro por unidad de negocio
  if (params.udnId) {
    query.where("udn.udnId", params.udnId);
  }

  // Filtro por categoría
  if (params.categoriaId) {
    query.whereExists(function (this: Knex.QueryBuilder) {
      this.select(db.raw("1"))
        .from("prod_productos_categorias as pc")
        .whereRaw("pc.productoId = p.productoId")
        .where("pc.flgDelete", 0)
        .where("pc.inventarioCategoriaId", params.categoriaId);
    });
  }
}
function baseProductQuery(params: ProductListQuery) {
  const latestPrice = latestPriceSubquery();

  const query = db("prod_productos as p")
    .leftJoin(latestPrice, "pp.productoId", "p.productoId")

    .leftJoin("cat_inventario_marcas as m", function () {
      this.on("m.marcaId", "=", "p.marcaId").andOn(
        "m.flgDelete",
        "=",
        db.raw("0"),
      );
    })

    .leftJoin("cat_unidades_medida as um", function () {
      this.on("um.unidadMedidaId", "=", "p.unidadMedidaId").andOn(
        "um.flgDelete",
        "=",
        db.raw("0"),
      );
    })

    .leftJoin("cat_inventario_tipos_producto as tp", function () {
      this.on("tp.tipoProductoId", "=", "p.tipoProductoId").andOn(
        "tp.flgDelete",
        "=",
        db.raw("0"),
      );
    })

    .leftJoin("cat_paises as pais", function () {
      this.on("pais.paisId", "=", "p.paisIdOrigen").andOn(
        "pais.flgDelete",
        "=",
        db.raw("0"),
      );
    })

    .leftJoin("fel_udn_marcas_detalle as udnm", function () {
      this.on("udnm.marcaId", "=", "p.marcaId").andOn(
        "udnm.flgDelete",
        "=",
        db.raw("0"),
      );
    })

    .leftJoin("cat_unidad_negocio as udn", function () {
      this.on("udn.udnId", "=", "udnm.udnId").andOn(
        "udn.flgDelete",
        "=",
        db.raw("0"),
      );
    });

  applyProductFilters(query, params);

  return query;
}

export const productRepository = {
  async findAll(params: ProductListQuery): Promise<{
    rows: ProductBaseRow[];
    total: number;
  }> {
    const offset = (params.page - 1) * params.limit;

    const countQuery = baseProductQuery(params)
      .clone()
      .clearSelect()
      .clearOrder()
      .countDistinct("p.productoId as total");
    const sortColumnMap: Record<ProductListQuery["sort"], string> = {
      productoId: "p.productoId",
      codInterno: "p.codInterno",
      codFabricante: "p.codFabricante",
      nombreProducto: "p.nombreProducto",
      precioVenta: "pp.precioVenta",
    };

    const sortColumn = sortColumnMap[params.sort];
    const dataQuery = baseProductQuery(params)
      .clone()
      .select(
        "p.productoId",

        "p.codFabricante",
        "p.codInterno",

        "p.nombreProducto",
        "p.descripcionProducto",
        "p.clasificacionMG",

        "p.marcaId",
        "m.nombreMarca",
        "m.abreviaturaMarca",
        "m.urlLogoMarca",
        "m.estadoMarca",

        "p.unidadMedidaId",
        "um.nombreUnidadMedida",
        "um.abreviaturaUnidadMedida",
        "um.codigoMH as codigoMHUnidadMedida",

        "p.tipoProductoId",
        "tp.nombreTipoProducto",

        "p.paisIdOrigen",
        "pais.pais",
        "pais.abreviaturaPais",

        "udn.udnId",
        "udn.nombreUDN",
        "udn.abreviaturaUDN",
        "udn.urlLogoUDN",
        "udn.estadoUDN",

        "p.estadoProducto",

        "pp.productoPrecioId",
        "pp.precioVenta",
      )
      .whereNotNull("pp.productoPrecioId")
      .orderBy(sortColumn, params.order)
      .limit(params.limit)
      .offset(offset);

    const [rows, countRows] = await Promise.all([dataQuery, countQuery]);

    const countResult = countRows[0] as { total: number | string };
    const total = Number(countResult?.total ?? 0);

    return {
      rows: rows as ProductBaseRow[],
      total,
    };
  },

  async findById(id: number): Promise<ProductBaseRow | undefined> {
    const params: ProductListQuery = {
      page: 1,
      limit: 1,
      sort: "nombreProducto",
      order: "asc",
    };
    const row = await baseProductQuery(params)
      .clone()
      .select(
        "p.productoId",
        "p.invProductoId",
        "p.codFabricante",
        "p.codInterno",
        "p.codMagic",
        "p.nombreProducto",
        "p.descripcionProducto",
        "p.clasificacionMG",

        "p.marcaId",
        "m.nombreMarca",
        "m.abreviaturaMarca",
        "m.urlLogoMarca",
        "m.estadoMarca",

        "p.unidadMedidaId",
        "um.nombreUnidadMedida",
        "um.abreviaturaUnidadMedida",
        "um.codigoMH as codigoMHUnidadMedida",

        "p.tipoProductoId",
        "tp.nombreTipoProducto",

        "p.paisIdOrigen",
        "pais.pais",
        "pais.abreviaturaPais",

        "udn.udnId",
        "udn.nombreUDN",
        "udn.abreviaturaUDN",
        "udn.urlLogoUDN",
        "udn.estadoUDN",

        "p.estadoProducto",
        "p.fhEstadoProducto",
        "p.tipoEstadoProducto",
        "p.obsEstadoProducto",

        "pp.productoPrecioId",
        "pp.precioVenta",
        "pp.estadoPrecio",
        "pp.fhAdd as fhPrecioAdd",
        "pp.fhEdit as fhPrecioEdit",

        "p.fechaApertura",
        "p.ultimaVenta",
        "p.ultimaCompra",
        "p.ultimoOtroMovimiento",

        "p.fhAdd",
        "p.fhEdit",
      )
      .where("p.productoId", id)
      .whereNotNull("pp.productoPrecioId")
      .first();

    return row as ProductBaseRow | undefined;
  },

  async findByCode(code: string): Promise<ProductBaseRow | undefined> {
    const params: ProductListQuery = {
      page: 1,
      limit: 1,
      sort: "nombreProducto",
      order: "asc",
    };

    const row = await baseProductQuery(params)
      .clone()
      .select(
        "p.productoId",
        "p.invProductoId",
        "p.codFabricante",
        "p.codInterno",
        "p.codMagic",
        "p.nombreProducto",
        "p.descripcionProducto",
        "p.clasificacionMG",

        "p.marcaId",
        "m.nombreMarca",
        "m.abreviaturaMarca",
        "m.urlLogoMarca",
        "m.estadoMarca",

        "p.unidadMedidaId",
        "um.nombreUnidadMedida",
        "um.abreviaturaUnidadMedida",
        "um.codigoMH as codigoMHUnidadMedida",

        "p.tipoProductoId",
        "tp.nombreTipoProducto",

        "p.paisIdOrigen",
        "pais.pais",
        "pais.abreviaturaPais",

        "udn.udnId",
        "udn.nombreUDN",
        "udn.abreviaturaUDN",
        "udn.urlLogoUDN",
        "udn.estadoUDN",

        "p.estadoProducto",
        "p.fhEstadoProducto",
        "p.tipoEstadoProducto",
        "p.obsEstadoProducto",

        "pp.productoPrecioId",
        "pp.precioVenta",
        "pp.estadoPrecio",
        "pp.fhAdd as fhPrecioAdd",
        "pp.fhEdit as fhPrecioEdit",

        "p.fechaApertura",
        "p.ultimaVenta",
        "p.ultimaCompra",
        "p.ultimoOtroMovimiento",

        "p.fhAdd",
        "p.fhEdit",
      )
      .where(function (this: Knex.QueryBuilder) {
        this.where("p.codInterno", "like", `%${code}%`)
          .orWhere("p.codFabricante", "like", `%${code}%`)
          .orWhere("p.nombreProducto", "like", `%${code}%`);
      })
      .whereNotNull("pp.productoPrecioId")
      .first();

    return row as ProductBaseRow | undefined;
  },

  async findCategoriesByProductIds(
    productIds: number[],
  ): Promise<ProductCategoryRow[]> {
    if (productIds.length === 0) return [];

    const rows = await db("prod_productos_categorias as pc")
      .innerJoin("cat_inventario_categorias as c", function () {
        this.on(
          "c.inventarioCategoriaId",
          "=",
          "pc.inventarioCategoriaId",
        ).andOn("c.flgDelete", "=", db.raw("0"));
      })
      .select(
        "pc.productoId",
        "c.inventarioCategoriaId",
        "c.abreviatura",
        "c.nombreCategoria",
      )
      .whereIn("pc.productoId", productIds)
      .where("pc.flgDelete", 0)
      .orderBy("c.nombreCategoria", "asc");

    return rows as ProductCategoryRow[];
  },

  async findSpecificationsByProductIds(
    productIds: number[],
  ): Promise<ProductSpecificationRow[]> {
    if (productIds.length === 0) return [];

    const rows = await db("prod_productos_especificaciones as pe")
      .leftJoin("cat_productos_especificaciones as ce", function () {
        this.on(
          "ce.catProdEspecificacionId",
          "=",
          "pe.catProdEspecificacionId",
        ).andOn("ce.flgDelete", "=", db.raw("0"));
      })
      .leftJoin("cat_unidades_medida as um", function () {
        this.on("um.unidadMedidaId", "=", "pe.unidadMedidaId").andOn(
          "um.flgDelete",
          "=",
          db.raw("0"),
        );
      })
      .select(
        "pe.productoId",
        "pe.prodEspecificacionId",
        "pe.catProdEspecificacionId",
        "ce.tipoEspecificacion",
        "ce.nombreProdEspecificacion",
        "ce.tipoMagnitud",
        "pe.valorEspecificacion",
        "pe.unidadMedidaId",
        "um.nombreUnidadMedida",
        "um.abreviaturaUnidadMedida",
      )
      .whereIn("pe.productoId", productIds)
      .where("pe.flgDelete", 0)
      .orderBy("ce.nombreProdEspecificacion", "asc");

    return rows as ProductSpecificationRow[];
  },

  async findExistencesByProductIds(
    productIds: number[],
  ): Promise<ProductExistenceRow[]> {
    if (productIds.length === 0) return [];

    const rows = await db("vw_existencias_sucursal_producto")
      .select("productoId", "sucursalId", "nombreSucursal", "existenciaTotal")
      .whereIn("productoId", productIds)
      .orderBy("productoId", "asc")
      .orderBy("nombreSucursal", "asc");

    return rows as ProductExistenceRow[];
  },
};
