const db = require('../models');
const { sendResponse, sendError } = require('../utils/response');
const asyncHandler = require('../middlewares/asyncHandler');
const { Op } = require('sequelize');

const parseNumberOrNull = (value) => {
  if (value === '' || value === null || value === undefined) return null;
  const n = Number(value);
  return Number.isFinite(n) ? n : null;
};

const withBendingDims = (target, source) => {
  for (let i = 1; i <= 10; i += 1) {
    const dimKey = `dim_${i}`;
    const tolKey = `tol_${i}`;
    target[dimKey] = parseNumberOrNull(source?.[dimKey]);
    target[tolKey] = source?.[tolKey] != null ? String(source[tolKey]) : '';
  }
  return target;
};

const normalizeMaterialDetails = (details) => {
  if (!Array.isArray(details)) return [];
  const map = new Map();
  details.forEach((row) => {
    const materialId = parseInt(row?.material_id || row?.materialId || row?.id || row?.value, 10);
    if (!Number.isFinite(materialId) || materialId <= 0) return;
    const legacyTol = row?.tol != null ? String(row.tol) : '';
    const normalizedRow = {
      material_id: materialId,
      qty: parseNumberOrNull(row?.qty ?? row?.quantity),
      width: parseNumberOrNull(row?.width),
      length: parseNumberOrNull(row?.length),
      height: parseNumberOrNull(row?.height),
      thickness: parseNumberOrNull(row?.thickness),
      tol_width: row?.tol_width != null ? String(row.tol_width) : legacyTol,
      tol_length: row?.tol_length != null ? String(row.tol_length) : legacyTol,
      tol_thickness: row?.tol_thickness != null ? String(row.tol_thickness) : legacyTol,
      qa_menus: Array.isArray(row?.qa_menus)
        ? Array.from(new Set(row.qa_menus.map((m) => String(m || '').trim().toUpperCase()).filter(Boolean)))
        : []
    };
    map.set(materialId, withBendingDims(normalizedRow, row));
  });
  return Array.from(map.values());
};

const serializeOperationMaterials = (operation) => {
  const detailsInput = Array.isArray(operation?.operation_material_details)
    ? operation.operation_material_details
    : [];
  const normalizedDetails = normalizeMaterialDetails(detailsInput);
  if (normalizedDetails.length > 0) {
    return JSON.stringify(normalizedDetails);
  }

  const ids = Array.isArray(operation?.operation_material_ids)
    ? operation.operation_material_ids
    : Array.isArray(operation?.operation_materials)
      ? operation.operation_materials
      : [];
  const cleaned = ids
    .map((v) => parseInt(v, 10))
    .filter((v) => Number.isFinite(v) && v > 0);
  if (cleaned.length === 0) return null;
  const uniqueIds = Array.from(new Set(cleaned));
  return JSON.stringify(uniqueIds.map((materialId) => ({
    material_id: materialId,
    qty: null,
    tol_width: '',
    tol_length: '',
    tol_thickness: ''
  })));
};

const parseOperationMaterials = (jsonText) => {
  if (!jsonText) return [];
  try {
    const arr = JSON.parse(jsonText);
    if (!Array.isArray(arr)) return [];
    const ids = [];
    arr.forEach((entry) => {
      if (entry == null) return;
      if (Number.isFinite(parseInt(entry, 10))) {
        ids.push(parseInt(entry, 10));
        return;
      }
      if (typeof entry === 'object') {
        const candidate = entry.material_id || entry.materialId || entry.id || entry.value;
        if (Number.isFinite(parseInt(candidate, 10))) ids.push(parseInt(candidate, 10));
      }
    });
    return Array.from(new Set(ids.filter((v) => Number.isFinite(v) && v > 0)));
  } catch (e) {
    return [];
  }
};

const parseOperationMaterialDetails = (jsonText) => {
  if (!jsonText) return [];
  try {
    const arr = JSON.parse(jsonText);
    if (!Array.isArray(arr)) return [];
    const asDetails = arr
      .map((entry) => {
        if (entry == null) return null;
        if (typeof entry === 'object') {
          const materialId = parseInt(entry.material_id || entry.materialId || entry.id || entry.value, 10);
          if (!Number.isFinite(materialId) || materialId <= 0) return null;
          const legacyTol = entry.tol != null ? String(entry.tol) : '';
          const normalizedEntry = {
            material_id: materialId,
            qty: parseNumberOrNull(entry.qty ?? entry.quantity),
            width: parseNumberOrNull(entry.width),
            length: parseNumberOrNull(entry.length),
            height: parseNumberOrNull(entry.height),
            thickness: parseNumberOrNull(entry.thickness),
            tol_width: entry.tol_width != null ? String(entry.tol_width) : legacyTol,
            tol_length: entry.tol_length != null ? String(entry.tol_length) : legacyTol,
            tol_thickness: entry.tol_thickness != null ? String(entry.tol_thickness) : legacyTol,
            qa_menus: Array.isArray(entry.qa_menus)
              ? Array.from(new Set(entry.qa_menus.map((m) => String(m || '').trim().toUpperCase()).filter(Boolean)))
              : []
          };
          return withBendingDims(normalizedEntry, entry);
        }
        const materialId = parseInt(entry, 10);
        if (!Number.isFinite(materialId) || materialId <= 0) return null;
        return {
          material_id: materialId,
          qty: null,
          tol_width: '',
          tol_length: '',
          tol_thickness: '',
          qa_menus: []
        };
      })
      .filter(Boolean);
    return normalizeMaterialDetails(asDetails);
  } catch (e) {
    return [];
  }
};

const withOperationMaterialIds = (routingLike) => {
  if (!routingLike) return routingLike;
  const json = typeof routingLike.toJSON === 'function' ? routingLike.toJSON() : { ...routingLike };
  json.operations = (json.operations || []).map((op) => ({
    ...op,
    operation_material_ids: parseOperationMaterials(op.operation_materials_json),
    operation_material_details: parseOperationMaterialDetails(op.operation_materials_json)
  }));
  return json;
};

/**
 * Attach department (from Operation Name master: same company + operation_code) to each operation.
 */
const attachOperationDepartments = async (routingJson) => {
  if (!routingJson || !Array.isArray(routingJson.operations)) return routingJson;
  const companyId = routingJson.company_id;
  const ops = routingJson.operations;
  if (!companyId || ops.length === 0) return routingJson;

  const codes = [...new Set(ops.map((o) => o && o.operation_code).filter(Boolean))];
  if (codes.length === 0) {
    routingJson.operations = ops.map((op) => ({ ...op, department: null }));
    return routingJson;
  }

  const rows = await db.OperationName.findAll({
    where: {
      company_id: companyId,
      operation_code: { [Op.in]: codes },
    },
    include: [
      {
        model: db.Department,
        as: 'department',
        attributes: ['id', 'department_code', 'department_name'],
        required: false,
      },
    ],
  });

  const byCode = new Map();
  rows.forEach((row) => {
    const j = typeof row.toJSON === 'function' ? row.toJSON() : row;
    if (j.operation_code != null && j.operation_code !== '' && !byCode.has(j.operation_code)) {
      byCode.set(j.operation_code, j.department || null);
    }
  });

  routingJson.operations = ops.map((op) => ({
    ...op,
    department: op.operation_code ? byCode.get(op.operation_code) ?? null : null,
  }));
  return routingJson;
};

exports.getAll = asyncHandler(async (req, res) => {
  const { page = 1, limit = 10, search, status, companyId, plantId } = req.query;
  const offset = (page - 1) * limit;

  const where = {};
  
  if (companyId) {
    where.company_id = companyId;
  } else if (req.user && !req.user.isSuperAdmin) {
    where.company_id = req.user.companyId;
  }

  if (plantId) {
    where.plant_id = plantId;
  }

  if (status) {
    where.status = status;
  } else {
    where.status = { [Op.ne]: 'DELETED' };
  }

  if (search) {
    where[Op.or] = [
      { routing_number: { [Op.like]: `%${search}%` } },
      { alternative: { [Op.like]: `%${search}%` } },
    ];
  }

  const { count, rows } = await db.Routing.findAndCountAll({
    where,
    limit: parseInt(limit),
    offset: parseInt(offset),
    order: [['id', 'DESC']],
    include: [
      { model: db.Company, as: 'company', attributes: ['id', 'code', 'name'] },
      { model: db.Plant, as: 'plant', attributes: ['id', 'code', 'name'] },
      { model: db.Material, as: 'material', attributes: ['id', 'material_number', 'description', 'drawing_number'] },
      { model: db.UOM, as: 'baseUOM', attributes: ['id', 'uom_code', 'uom_name'] },
    ],
  });

  sendResponse(res, 200, true, 'Routings retrieved successfully', {
    items: rows,
    totalItems: count,
    totalPages: Math.ceil(count / limit),
    currentPage: parseInt(page),
  });
});

exports.getById = asyncHandler(async (req, res) => {
  const { id } = req.params;

  const routing = await db.Routing.findByPk(id, {
    include: [
      { model: db.Company, as: 'company', attributes: ['id', 'code', 'name'] },
      { model: db.Plant, as: 'plant', attributes: ['id', 'code', 'name'] },
      { model: db.Material, as: 'material', attributes: ['id', 'material_number', 'description', 'drawing_number'] },
      { model: db.UOM, as: 'baseUOM', attributes: ['id', 'uom_code', 'uom_name'] },
      {
        model: db.RoutingOperation,
        as: 'operations',
        include: [
          { model: db.WorkCenter, as: 'workCenter', attributes: ['id', 'work_center_code', 'description'] },
          { model: db.Machine, as: 'machine', attributes: ['id', 'machine_code', 'machine_description'] },
        ],
        order: [['operation_number', 'ASC']],
      },
    ],
  });

  if (!routing) {
    return sendError(res, 'Routing not found', 404);
  }

  const payload = await attachOperationDepartments(withOperationMaterialIds(routing));
  sendResponse(res, 200, true, 'Routing retrieved successfully', payload);
});

exports.create = asyncHandler(async (req, res) => {
  const data = req.body;
  const userId = req.user?.userId || null;
  const { operations, ...routingData } = data;

  const transaction = await db.sequelize.transaction();

  try {
    routingData.created_by = userId;
    routingData.updated_by = userId;

    if (routingData.company_id === undefined && req.user) {
      routingData.company_id = req.user.companyId;
    }

    const routing = await db.Routing.create(routingData, { transaction });

    if (operations && Array.isArray(operations) && operations.length > 0) {
      const routingOperations = operations.map((operation, index) => ({
        routing_id: routing.id,
        operation_number: operation.operation_number || (index + 1) * 10,
        operation_code: operation.operation_code || null,
        work_center_id: operation.work_center_id,
        machine_id: operation.machine_id || null,
        operation_drawing_ref: operation.operation_drawing_ref || null,
        operation_materials_json: serializeOperationMaterials(operation),
        operation_description: operation.operation_description || null,
        standard_text_key: operation.standard_text_key || null,
        control_key: operation.control_key || null,
        setup_time: parseInt(operation.setup_time) || 0,
        machine_time: parseFloat(operation.machine_time) || 0,
        labor_time: parseFloat(operation.labor_time) || 0,
        base_quantity: parseFloat(operation.base_quantity) || routing.base_quantity || 1,
        queue_time: parseInt(operation.queue_time) || 0,
        move_time: parseInt(operation.move_time) || 0,
        status: operation.status || 'ACTIVE',
        created_by: userId,
        updated_by: userId,
      }));

      await db.RoutingOperation.bulkCreate(routingOperations, { transaction });
    }

    await transaction.commit();

    const createdRouting = await db.Routing.findByPk(routing.id, {
      include: [
        { model: db.Company, as: 'company', attributes: ['id', 'code', 'name'] },
        { model: db.Plant, as: 'plant', attributes: ['id', 'code', 'name'] },
        { model: db.Material, as: 'material', attributes: ['id', 'material_number', 'description', 'drawing_number'] },
        { model: db.UOM, as: 'baseUOM', attributes: ['id', 'uom_code', 'uom_name'] },
        {
          model: db.RoutingOperation,
          as: 'operations',
          include: [
            { model: db.WorkCenter, as: 'workCenter', attributes: ['id', 'work_center_code', 'description'] },
            { model: db.Machine, as: 'machine', attributes: ['id', 'machine_code', 'machine_description'] },
          ],
          order: [['operation_number', 'ASC']],
        },
      ],
    });

    const payload = await attachOperationDepartments(withOperationMaterialIds(createdRouting));
    sendResponse(res, 201, true, 'Routing created successfully', payload);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

exports.update = asyncHandler(async (req, res) => {
  const { id } = req.params;
  const data = req.body;
  const userId = req.user?.userId || null;
  const { operations, ...routingData } = data;

  const routing = await db.Routing.findByPk(id);
  if (!routing) {
    return sendError(res, 'Routing not found', 404);
  }

  if (routing.status === 'LOCKED') {
    return sendError(res, 'Locked Routing cannot be updated', 400);
  }

  const transaction = await db.sequelize.transaction();

  try {
    routingData.updated_by = userId;

    await routing.update(routingData, { transaction });

    if (operations !== undefined) {
      await db.RoutingOperation.destroy({
        where: { routing_id: id },
        transaction,
      });

      if (Array.isArray(operations) && operations.length > 0) {
        const routingOperations = operations.map((operation, index) => ({
          routing_id: id,
          operation_number: operation.operation_number || (index + 1) * 10,
          operation_code: operation.operation_code || null,
          work_center_id: operation.work_center_id,
          machine_id: operation.machine_id || null,
          operation_drawing_ref: operation.operation_drawing_ref || null,
          operation_materials_json: serializeOperationMaterials(operation),
          operation_description: operation.operation_description || null,
          standard_text_key: operation.standard_text_key || null,
          control_key: operation.control_key || null,
          setup_time: parseInt(operation.setup_time) || 0,
          machine_time: parseFloat(operation.machine_time) || 0,
          labor_time: parseFloat(operation.labor_time) || 0,
          base_quantity: parseFloat(operation.base_quantity) || routing.base_quantity || 1,
          queue_time: parseInt(operation.queue_time) || 0,
          move_time: parseInt(operation.move_time) || 0,
          status: operation.status || 'ACTIVE',
          created_by: userId,
          updated_by: userId,
        }));

        await db.RoutingOperation.bulkCreate(routingOperations, { transaction });
      }
    }

    await transaction.commit();

    const updatedRouting = await db.Routing.findByPk(id, {
      include: [
        { model: db.Company, as: 'company', attributes: ['id', 'code', 'name'] },
        { model: db.Plant, as: 'plant', attributes: ['id', 'code', 'name'] },
        { model: db.Material, as: 'material', attributes: ['id', 'material_number', 'description', 'drawing_number'] },
        { model: db.UOM, as: 'baseUOM', attributes: ['id', 'uom_code', 'uom_name'] },
        {
          model: db.RoutingOperation,
          as: 'operations',
          include: [
            { model: db.WorkCenter, as: 'workCenter', attributes: ['id', 'work_center_code', 'description'] },
            { model: db.Machine, as: 'machine', attributes: ['id', 'machine_code', 'machine_description'] },
          ],
          order: [['operation_number', 'ASC']],
        },
      ],
    });

    const payload = await attachOperationDepartments(withOperationMaterialIds(updatedRouting));
    sendResponse(res, 200, true, 'Routing updated successfully', payload);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

exports.delete = asyncHandler(async (req, res) => {
  const { id } = req.params;
  const userId = req.user?.userId || null;

  const routing = await db.Routing.findByPk(id);
  if (!routing) {
    return sendError(res, 'Routing not found', 404);
  }

  if (routing.status === 'LOCKED') {
    return sendError(res, 'Locked Routing cannot be deleted', 400);
  }

  const transaction = await db.sequelize.transaction();

  try {
    routing.status = 'INACTIVE';
    routing.updated_by = userId;
    await routing.save({ transaction });

    await transaction.commit();

    sendResponse(res, 200, true, 'Routing deleted successfully', null);
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
});

