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

const parseJSON = (value, fallback = []) => {
  if (!value) return fallback;
  try {
    const parsed = JSON.parse(value);
    return Array.isArray(parsed) ? parsed : fallback;
  } catch (_) {
    return fallback;
  }
};

const toNum = (value) => {
  const n = Number(value);
  return Number.isFinite(n) ? n : null;
};

const toTextOrNull = (value) => {
  if (value === undefined || value === null) return null;
  const v = String(value).trim();
  return v === '' ? null : v;
};

const mapPressBrakeFields = (component = {}) => {
  const payload = {};
  for (let i = 1; i <= 10; i += 1) {
    payload[`pb_dim_${i}`] = toTextOrNull(component[`pb_dim_${i}`]);
    payload[`pb_tol_${i}`] = toTextOrNull(component[`pb_tol_${i}`]);
  }
  return payload;
};

const QA_MENU_OPTIONS = [
  { key: 'INCOMING_INSPECTION', label: 'Incoming Inspection Report' },
  { key: 'DIMENSIONAL_REPORT', label: 'Dimensional Report' },
  { key: 'DIMENSIONAL_REPORT_PRESS_BRAKE', label: 'Dimensional Report (PRESS BRAKE)' },
  { key: 'IN_PROCESS_CHECKLIST', label: 'In Process Check List' },
  { key: 'WELDING_FINAL_INSPECTION', label: 'Welding Final Inspection' },
  { key: 'WELDING_VISUAL_INSPECTION', label: 'Welding - Visual Inspection Report' },
  { key: 'ASSEMBLY_FINAL_INSPECTION', label: 'Assembly Final Inspection' },
  { key: 'MEASURING_INSTRUMENTS', label: 'Measuring Instruments' },
  { key: 'MEASURING_INSTRUMENT_HISTORY_CARD', label: 'Measuring Instrument History Card' },
  { key: 'REJECTION_REWORK', label: 'Rejection / Rework Record' },
  { key: 'CUSTOMER_COMPLAINT', label: 'Customer Complaint Register' },
  { key: 'CORRECTIVE_ACTION', label: 'Corrective Action Report' }
];

const getBOMChildrenRecursive = async ({
  bomId,
  level = 0,
  path = '',
  visitedBOMs = new Set(),
  latestBomByMaterialId = new Map(),
  bomComponentCache = new Map()
}) => {
  if (visitedBOMs.has(bomId)) return [];
  visitedBOMs.add(bomId);

  let components = bomComponentCache.get(bomId);
  if (!components) {
    components = await db.BOMComponent.findAll({
      where: { bom_id: bomId, status: 'ACTIVE' },
      attributes: ['id', 'item_number', 'material_id'],
      include: [{ model: db.Material, as: 'material', attributes: ['id', 'material_number', 'description', 'width', 'length', 'height', 'thickness'] }],
      order: [['item_number', 'ASC']]
    });
    bomComponentCache.set(bomId, components);
  }

  const result = [];
  for (const c of components) {
    const itemCode = c.material?.material_number || '';
    const itemName = c.material?.description || '';
    const nodePath = path ? `${path} > ${itemCode}` : itemCode;

    const childBomId = latestBomByMaterialId.get(c.material_id) || null;

    result.push({
      id: c.id,
      item_number: c.item_number,
      material_id: c.material_id,
      item_code: itemCode,
      item_name: itemName,
      width: toNum(c.material?.width),
      length: toNum(c.material?.length),
      height: toNum(c.material?.height),
      thickness: toNum(c.material?.thickness),
      level,
      hierarchy_path: nodePath,
      has_children: Boolean(childBomId)
    });

    if (childBomId) {
      const childRows = await getBOMChildrenRecursive({
        bomId: childBomId,
        level: level + 1,
        path: nodePath,
        visitedBOMs,
        latestBomByMaterialId,
        bomComponentCache
      });
      result.push(...childRows);
    }
  }

  visitedBOMs.delete(bomId);
  return result;
};

exports.getMetaOptions = asyncHandler(async (req, res) => {
  const companyId = req.query.companyId || req.user?.companyId;
  const plantId = parseInt(req.query.plantId, 10);

  if (!companyId || !Number.isFinite(plantId)) {
    return sendError(res, 'companyId and plantId are required', 400);
  }

  const [operations, boms, materials] = await Promise.all([
    db.OperationName.findAll({
      where: { company_id: companyId, status: 'ACTIVE' },
      attributes: ['id', 'operation_code', 'operation_no', 'description'],
      order: [['operation_no', 'ASC'], ['operation_code', 'ASC']]
    }),
    db.BOMHeader.findAll({
      where: { company_id: companyId, plant_id: plantId, status: 'ACTIVE' },
      attributes: ['id', 'bom_number', 'material_id'],
      include: [{ model: db.Material, as: 'material', attributes: ['id', 'material_number', 'description'] }],
      order: [['id', 'DESC']]
    }),
    db.Material.findAll({
      where: { company_id: companyId, status: 'ACTIVE' },
      attributes: ['id', 'material_number', 'description', 'material_group'],
      include: [{ model: db.MaterialGroup, as: 'materialGroup', attributes: ['id', 'group_code', 'group_name'], required: false }],
      order: [['material_number', 'ASC']]
    })
  ]);

  const operationOptions = operations.map((op) => ({
    id: op.id,
    operation_no: op.operation_no,
    operation_code: op.operation_code,
    description: op.description || '',
    label: `${op.operation_no || '-'} - ${op.operation_code}${op.description ? ` - ${op.description}` : ''}`
  }));

  const bomOptions = boms.map((bom) => ({
    id: bom.id,
    bom_number: bom.bom_number,
    material_id: bom.material_id,
    item_code: bom.material?.material_number || '',
    item_name: bom.material?.description || '',
    label: `${bom.material?.material_number || ''} - ${bom.material?.description || ''} (BOM: ${bom.bom_number})`
  }));

  const powderCoatingOptions = (materials || [])
    .filter((m) => {
      const groupName = `${m.materialGroup?.group_name || ''} ${m.materialGroup?.group_code || ''} ${m.material_group || ''}`.toUpperCase();
      return groupName.includes('POWDER') && groupName.includes('COAT');
    })
    .map((m) => ({
      id: m.id,
      material_number: m.material_number || '',
      description: m.description || '',
      label: `${m.material_number || ''} - ${m.description || ''}`
    }));

  return sendSuccess(res, 'QA master options fetched successfully', {
    operationOptions,
    bomOptions,
    qaMenuOptions: QA_MENU_OPTIONS,
    powderCoatingOptions
  });
});

exports.getBOMComponents = asyncHandler(async (req, res) => {
  const bomId = parseInt(req.params.bomId, 10);
  const flat = String(req.query?.flat || '').toLowerCase() === 'true';
  const qaMenuKey = String(req.query?.qa_menu_key || '').toUpperCase();
  if (!Number.isFinite(bomId) || bomId <= 0) {
    return sendError(res, 'Valid bomId is required', 400);
  }

  const header = await db.BOMHeader.findByPk(bomId, {
    attributes: ['id', 'company_id', 'plant_id', 'material_id'],
    include: [{ model: db.Material, as: 'material', attributes: ['id', 'material_number', 'description', 'width', 'length', 'height', 'thickness'], required: false }]
  });
  if (!header) return sendError(res, 'BOM header not found', 404);

  let payload = [];
  if (qaMenuKey === 'ASSEMBLY_FINAL_INSPECTION') {
    const rootRow = {
      id: `ROOT_${header.id}`,
      bom_component_id: null,
      item_number: 'ROOT',
      material_id: header.material_id,
      item_code: header.material?.material_number || '',
      item_name: header.material?.description || '',
      width: toNum(header.material?.width),
      length: toNum(header.material?.length),
      height: toNum(header.material?.height),
      thickness: toNum(header.material?.thickness),
      level: 0,
      hierarchy_path: header.material?.material_number || '',
      has_children: false
    };
    payload = [rootRow];
  } else if (flat) {
    const directComponents = await db.BOMComponent.findAll({
      where: { bom_id: header.id, status: 'ACTIVE' },
      attributes: ['id', 'item_number', 'material_id'],
      include: [{ model: db.Material, as: 'material', attributes: ['id', 'material_number', 'description', 'width', 'length', 'height', 'thickness'] }],
      order: [['item_number', 'ASC']]
    });
    payload = directComponents.map((c) => ({
      id: c.id,
      item_number: c.item_number,
      material_id: c.material_id,
      item_code: c.material?.material_number || '',
      item_name: c.material?.description || '',
      width: toNum(c.material?.width),
      length: toNum(c.material?.length),
      height: toNum(c.material?.height),
      thickness: toNum(c.material?.thickness),
      level: 0,
      hierarchy_path: c.material?.material_number || '',
      has_children: false
    }));
  } else {
    // Preload latest active BOM by material once to avoid per-component DB calls.
    const bomHeaders = await db.BOMHeader.findAll({
      where: {
        company_id: header.company_id,
        plant_id: header.plant_id,
        status: 'ACTIVE'
      },
      attributes: ['id', 'material_id'],
      order: [['id', 'DESC']]
    });
    const latestBomByMaterialId = new Map();
    for (const b of bomHeaders) {
      if (!latestBomByMaterialId.has(b.material_id)) {
        latestBomByMaterialId.set(b.material_id, b.id);
      }
    }

    payload = await getBOMChildrenRecursive({
      bomId: header.id,
      latestBomByMaterialId
    });
  }

  return sendSuccess(res, 'BOM components fetched successfully', payload);
});

exports.getAll = asyncHandler(async (req, res) => {
  const { page = 1, limit = 25, search = '', status, companyId, plantId } = req.query;
  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;

  if (search) {
    where[Op.or] = [{ '$itemMaterial.material_number$': { [Op.like]: `%${search}%` } }, { '$itemMaterial.description$': { [Op.like]: `%${search}%` } }, { '$operationName.operation_code$': { [Op.like]: `%${search}%` } }];
  }

  const offset = (parseInt(page, 10) - 1) * parseInt(limit, 10);
  const { count, rows } = await db.QAMaster.findAndCountAll({
    where,
    include: [
      { model: db.OperationName, as: 'operationName', attributes: ['id', 'operation_no', 'operation_code', 'description'] },
      { model: db.BOMHeader, as: 'bom', attributes: ['id', 'bom_number'] },
      { model: db.Material, as: 'itemMaterial', attributes: ['id', 'material_number', 'description'] }
    ],
    order: [['id', 'DESC']],
    limit: parseInt(limit, 10),
    offset,
    distinct: true,
    subQuery: false
  });

  const items = rows.map((row) => ({
    ...row.toJSON(),
    qa_menus: parseJSON(row.qa_menus, []),
    powder_coating_grade_ids: parseJSON(row.powder_coating_grade_ids, [])
  }));

  return sendSuccess(res, 'QA masters fetched successfully', {
    items,
    pagination: {
      total: count,
      page: parseInt(page, 10),
      limit: parseInt(limit, 10),
      pages: Math.ceil(count / parseInt(limit, 10))
    }
  });
});

exports.getById = asyncHandler(async (req, res) => {
  const row = await db.QAMaster.findByPk(req.params.id, {
    include: [
      { model: db.OperationName, as: 'operationName', attributes: ['id', 'operation_no', 'operation_code', 'description'] },
      { model: db.BOMHeader, as: 'bom', attributes: ['id', 'bom_number'] },
      { model: db.Material, as: 'itemMaterial', attributes: ['id', 'material_number', 'description'] },
      { model: db.QAMasterComponent, as: 'components', include: [{ model: db.Material, as: 'material', attributes: ['id', 'material_number', 'description'] }] }
    ]
  });
  if (!row) return sendError(res, 'QA master not found', 404);

  const payload = row.toJSON();
  payload.qa_menus = parseJSON(payload.qa_menus, []);
  payload.powder_coating_grade_ids = parseJSON(payload.powder_coating_grade_ids, []);
  payload.components = (payload.components || []).sort((a, b) => (a.line_no || 0) - (b.line_no || 0));
  return sendSuccess(res, 'QA master fetched successfully', payload);
});

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

  if (!companyId) return sendError(res, 'Company ID is required', 400);
  if (!data.plant_id) return sendError(res, 'plant_id is required', 400);
  if (!data.operation_name_id) return sendError(res, 'operation_name_id is required', 400);
  if (!data.bom_id) return sendError(res, 'bom_id is required', 400);
  if (!data.item_material_id) return sendError(res, 'item_material_id is required', 400);

  const selectedComponents = Array.isArray(data.components) ? data.components : [];
  if (!selectedComponents.length) return sendError(res, 'Select at least one BOM component', 400);

  const t = await db.sequelize.transaction();
  try {
    const op = await db.OperationName.findByPk(data.operation_name_id, { transaction: t });
    if (!op) throw new Error('Operation not found');

    const created = await db.QAMaster.create({
      company_id: companyId,
      plant_id: data.plant_id,
      operation_name_id: data.operation_name_id,
      operation_no: data.operation_no || op.operation_no || null,
      bom_id: data.bom_id,
      item_material_id: data.item_material_id,
      qa_menus: JSON.stringify(Array.isArray(data.qa_menus) ? data.qa_menus : []),
      powder_coating_grade_ids: JSON.stringify(Array.isArray(data.powder_coating_grade_ids) ? data.powder_coating_grade_ids : []),
      status: data.status || 'ACTIVE',
      created_by: userId,
      updated_by: userId
    }, { transaction: t });

    await db.QAMasterComponent.bulkCreate(selectedComponents.map((c, idx) => ({
      qa_master_id: created.id,
      bom_component_id: c.bom_component_id || c.id || null,
      line_no: idx + 1,
      material_id: c.material_id,
      width: toNum(c.width),
      width_tol: toTextOrNull(c.width_tol ?? c.tol),
      length: toNum(c.length),
      length_tol: toTextOrNull(c.length_tol ?? c.tol),
      height: toNum(c.height),
      height_tol: toTextOrNull(c.height_tol ?? c.tol),
      thickness: toNum(c.thickness),
      thickness_tol: toTextOrNull(c.thickness_tol ?? c.tol),
      tol: toTextOrNull(c.tol),
      ...mapPressBrakeFields(c)
    })), { transaction: t });

    await t.commit();
    const saved = await db.QAMaster.findByPk(created.id, { include: [{ model: db.QAMasterComponent, as: 'components' }] });
    return sendSuccess(res, 'QA master created successfully', {
      ...saved.toJSON(),
      qa_menus: parseJSON(saved.qa_menus, []),
      powder_coating_grade_ids: parseJSON(saved.powder_coating_grade_ids, [])
    }, null, 201);
  } catch (e) {
    await t.rollback();
    return sendError(res, e.message || 'Failed to create QA master', 400);
  }
});

exports.update = asyncHandler(async (req, res) => {
  const id = parseInt(req.params.id, 10);
  const data = req.body || {};
  const userId = req.user?.id || req.user?.userId || null;
  const row = await db.QAMaster.findByPk(id);
  if (!row) return sendError(res, 'QA master not found', 404);

  const selectedComponents = Array.isArray(data.components) ? data.components : [];
  if (!selectedComponents.length) return sendError(res, 'Select at least one BOM component', 400);

  const t = await db.sequelize.transaction();
  try {
    const op = data.operation_name_id ? await db.OperationName.findByPk(data.operation_name_id, { transaction: t }) : null;

    await row.update({
      operation_name_id: data.operation_name_id || row.operation_name_id,
      operation_no: data.operation_no || op?.operation_no || row.operation_no,
      bom_id: data.bom_id || row.bom_id,
      item_material_id: data.item_material_id || row.item_material_id,
      qa_menus: JSON.stringify(Array.isArray(data.qa_menus) ? data.qa_menus : parseJSON(row.qa_menus, [])),
      powder_coating_grade_ids: JSON.stringify(Array.isArray(data.powder_coating_grade_ids) ? data.powder_coating_grade_ids : parseJSON(row.powder_coating_grade_ids, [])),
      status: data.status || row.status,
      updated_by: userId
    }, { transaction: t });

    await db.QAMasterComponent.destroy({ where: { qa_master_id: row.id }, transaction: t });
    await db.QAMasterComponent.bulkCreate(selectedComponents.map((c, idx) => ({
      qa_master_id: row.id,
      bom_component_id: c.bom_component_id || c.id || null,
      line_no: idx + 1,
      material_id: c.material_id,
      width: toNum(c.width),
      width_tol: toTextOrNull(c.width_tol ?? c.tol),
      length: toNum(c.length),
      length_tol: toTextOrNull(c.length_tol ?? c.tol),
      height: toNum(c.height),
      height_tol: toTextOrNull(c.height_tol ?? c.tol),
      thickness: toNum(c.thickness),
      thickness_tol: toTextOrNull(c.thickness_tol ?? c.tol),
      tol: toTextOrNull(c.tol),
      ...mapPressBrakeFields(c)
    })), { transaction: t });

    await t.commit();
    const saved = await db.QAMaster.findByPk(row.id, { include: [{ model: db.QAMasterComponent, as: 'components' }] });
    return sendSuccess(res, 'QA master updated successfully', {
      ...saved.toJSON(),
      qa_menus: parseJSON(saved.qa_menus, []),
      powder_coating_grade_ids: parseJSON(saved.powder_coating_grade_ids, [])
    });
  } catch (e) {
    await t.rollback();
    return sendError(res, e.message || 'Failed to update QA master', 400);
  }
});

exports.delete = asyncHandler(async (req, res) => {
  const id = parseInt(req.params.id, 10);
  const row = await db.QAMaster.findByPk(id);
  if (!row) return sendError(res, 'QA master not found', 404);
  await row.destroy();
  return sendSuccess(res, 'QA master deleted successfully');
});

