import { Request, Response } from 'express';
import StockItem from '../models/StockItem.js';
import StockMovement from '../models/StockMovement.js';

import { IAppUser } from '../middleware/authMiddleware.js';
import { notificationService } from '../services/notificationService.js';

interface AuthRequest extends Request {
    appUser?: IAppUser;
}

export const createStockItem = async (req: AuthRequest, res: Response) => {
    try {
        const organizationId = req.appUser?.organizationId;
        const userName = req.appUser?.name || req.appUser?.email || 'Unknown User';
        const {
            dataSheetId,
            rrNumber,
            batchNumber,
            quantity,
            unit,
            expirationDate,
            notes,
            color,
            invoiceNumber,
            receivedBy,
            minStock
        } = req.body;

        // Validation
        if (!dataSheetId || !rrNumber || !batchNumber || quantity === undefined || !unit) {
            return res.status(400).json({ error: 'Campos obrigatórios: DataSheet, RR, Lote, Quantidade, Unidade.' });
        }

        // Check for duplicate RR within Org
        const existing = await StockItem.findOne({ organizationId, rrNumber });
        if (existing) {
            return res.status(400).json({ error: `Já existe um item com o RR ${rrNumber}.` });
        }

        // --- Min Stock Inheritance Logic ---
        let finalMinStock = Number(minStock) || 0;

        // If user didn't provide a specific minStock (or provided 0), try to inherit from existing group
        if (finalMinStock === 0) {
            const existingGroupItem = await StockItem.findOne({
                organizationId,
                dataSheetId,
                color
            }).sort({ updatedAt: -1 }); // Get latest active config

            if (existingGroupItem && existingGroupItem.minStock > 0) {
                finalMinStock = existingGroupItem.minStock;
            }
        } else {
            // If user DID provide a minStock, update all existing items in that group to match?
            // User requested: "a regra de estoque minimo definido no cadastro precisa estar clonado para novos cadastros"
            // And "soma dessas 'mesmas' tintas sejam comparadas com o estoque minimo cadastrado a elas"
            // This implies the rule is a Property of the Group. So create/update should enforce consistency.
            if (finalMinStock > 0) {
                await StockItem.updateMany(
                    { organizationId, dataSheetId, color },
                    { $set: { minStock: finalMinStock } }
                );
            }
        }

        const newItem = new StockItem({
            organizationId,
            createdBy: req.appUser?.clerkId,
            dataSheetId,
            rrNumber,
            batchNumber,
            quantity: Number(quantity),
            unit,
            minStock: finalMinStock,
            expirationDate,
            notes,
            color,
            invoiceNumber,
            receivedBy
        });

        const savedItem = await newItem.save();

        // Create Initial Movement (ENTRY)
        await StockMovement.create({
            organizationId,
            createdBy: req.appUser?.clerkId,
            stockItemId: savedItem._id,
            movementNumber: 1,
            type: 'ENTRY',
            quantity: Number(quantity),
            responsible: userName,
            notes: 'Abertura de Lote / Entrada Inicial'
        });

        // Notificação de Recebimento
        if (organizationId) {
            await notificationService.create({
                organizationId,
                title: 'Recebimento de Material',
                message: `Recebido: ${quantity}${unit} de ${savedItem.rrNumber} (Lote: ${batchNumber}).`,
                type: 'info',
                metadata: { stockItemId: savedItem._id, triggerType: 'stock_received' }
            });
        }

        // Check Low Stock immediately
        await notificationService.checkLowStock(savedItem._id.toString());

        res.status(201).json(savedItem);
    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        console.error('Error creating stock item:', error);
        res.status(500).json({ error: message });
    }
};

export const updateStockItem = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const organizationId = req.appUser?.organizationId;
        // Only allow updating metadata, NOT quantity directly (quantity must be via adjustments)
        // Adjusting logic: Admin might need to fix typo in quantity without movement record? 
        // Better enforcing movements. If quantity changes, user should use "Adjustment".
        // Here we create a general update for details like Notes, Dates, etc.

        const { quantity, ...otherData } = req.body; // Separate quantity

        if (quantity !== undefined) {
            return res.status(400).json({ error: 'Para alterar a quantidade, utilize as funções de Ajuste ou Consumo.' });
        }

        // Check if Min Stock is being updated
        if (otherData.minStock !== undefined) {
            const item = await StockItem.findOne({ _id: id, organizationId });
            if (item) {
                // Propagate to all siblings (same Product + Color)
                await StockItem.updateMany(
                    {
                        organizationId,
                        dataSheetId: item.dataSheetId,
                        color: item.color
                    },
                    { $set: { minStock: otherData.minStock } }
                );
            }
        }

        const updated = await StockItem.findOneAndUpdate(
            { _id: id, organizationId },
            otherData,
            { new: true }
        );

        if (!updated) return res.status(404).json({ error: 'Item não encontrado.' });

        // Check Low Stock (in case minStock changed)
        await notificationService.checkLowStock(updated._id.toString());

        res.json(updated);

    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        res.status(500).json({ error: message });
    }
};

export const adjustStock = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const organizationId = req.appUser?.organizationId;
        const userName = req.appUser?.name || req.appUser?.email || 'Unknown User';
        const { quantityDelta, reason } = req.body; // quantityDelta: +10 or -5

        if (!reason) return res.status(400).json({ error: 'Motivo é obrigatório para ajustes técnicos.' });
        if (!quantityDelta || isNaN(quantityDelta)) return res.status(400).json({ error: 'Quantidade inválida.' });

        const item = await StockItem.findOne({ _id: id, organizationId });
        if (!item) return res.status(404).json({ error: 'Item não encontrado.' });

        // Calculate new quantity
        const newQuantity = Number(item.quantity) + Number(quantityDelta);
        if (newQuantity < 0) return res.status(400).json({ error: 'Estoque insuficiente para este ajuste.' });

        item.quantity = newQuantity;
        await item.save();

        // Calculate next movement number
        const lastMov = await StockMovement.findOne({ stockItemId: item._id }).sort({ movementNumber: -1 });
        const count = await StockMovement.countDocuments({ stockItemId: item._id });
        const movementNumber = (lastMov?.movementNumber || count) + 1;

        // Register Movement
        await StockMovement.create({
            organizationId,
            createdBy: req.appUser?.clerkId,
            stockItemId: item._id,
            movementNumber,
            type: 'ADJUSTMENT',
            quantity: Number(quantityDelta),
            responsible: userName,
            reason
        });

        // Check Low Stock
        await notificationService.checkLowStock(item._id.toString());

        res.json(item);

    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        res.status(500).json({ error: message });
    }
};

export const consumeStock = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const organizationId = req.appUser?.organizationId;
        const userName = req.appUser?.name || req.appUser?.email || 'Unknown User';
        const { quantityConsumed, requester, date } = req.body;

        if (!requester) return res.status(400).json({ error: 'Solicitante é obrigatório.' });
        if (!quantityConsumed || Number(quantityConsumed) <= 0) return res.status(400).json({ error: 'Quantidade deve ser maior que zero.' });

        const item = await StockItem.findOne({ _id: id, organizationId });
        if (!item) return res.status(404).json({ error: 'Item não encontrado.' });

        if (item.quantity < Number(quantityConsumed)) return res.status(400).json({ error: 'Estoque insuficiente.' });

        item.quantity -= Number(quantityConsumed);
        await item.save();

        // Calculate next movement number
        const lastMov = await StockMovement.findOne({ stockItemId: item._id }).sort({ movementNumber: -1 });
        const count = await StockMovement.countDocuments({ stockItemId: item._id });
        const movementNumber = (lastMov?.movementNumber || count) + 1;

        // Register Movement (Negative quantity for consumption)
        await StockMovement.create({
            organizationId,
            createdBy: req.appUser?.clerkId,
            stockItemId: item._id,
            movementNumber,
            type: 'CONSUMPTION',
            quantity: -Number(quantityConsumed), // Negative
            responsible: userName,
            requester,
            date: date || new Date()
        });

        // Check Low Stock
        await notificationService.checkLowStock(item._id.toString());

        res.json(item);

    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        res.status(500).json({ error: message });
    }
};

export const deleteStockItem = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const organizationId = req.appUser?.organizationId;

        // Optional: Block delete if there are movements other than ENTRY?
        // For simplicity allow Admin to nuke it.

        const deleted = await StockItem.findOneAndDelete({ _id: id, organizationId });
        if (!deleted) return res.status(404).json({ error: 'Item não encontrado.' });

        // Cleanup movements & logs
        await StockMovement.deleteMany({ stockItemId: id });
        await StockAuditLog.deleteMany({ stockItemId: id });

        res.status(204).send();
    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        res.status(500).json({ error: message });
    }
};

export const getStockItems = async (req: AuthRequest, res: Response) => {
    try {
        const organizationId = req.appUser?.organizationId;
        const { dataSheetId } = req.query;

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const query: any = { organizationId };
        if (dataSheetId) query.dataSheetId = dataSheetId;

        // Sort by Expiration Date ASC (First to expire first)
        const items = await StockItem.find(query)
            .populate('dataSheetId', 'name manufacturer type')
            .sort({ expirationDate: 1 });

        res.json(items);
    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        res.status(500).json({ error: message });
    }
};

export const getStockItemById = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const organizationId = req.appUser?.organizationId;

        const item = await StockItem.findOne({ _id: id, organizationId })
            .populate('dataSheetId', 'name manufacturer type');

        if (!item) return res.status(404).json({ error: 'Item não encontrado.' });

        res.json(item);
    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        res.status(500).json({ error: message });
    }
};

export const getStockMovements = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params; // StockItem ID
        const organizationId = req.appUser?.organizationId;

        const movements = await StockMovement.find({ stockItemId: id, organizationId })
            .sort({ date: -1 });

        res.json(movements);
    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        res.status(500).json({ error: message });
    }
};

// ------------------------------------------------------------------
// CRUD & Auditing for Movements
// ------------------------------------------------------------------

import StockAuditLog from '../models/StockAuditLog.js';

export const updateStockMovement = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params; // Movement ID
        const organizationId = req.appUser?.organizationId;
        const userName = req.appUser?.name || req.appUser?.email || 'Unknown User';
        const userId = req.appUser?.clerkId || 'system';
        const isAdmin = req.appUser?.role === 'admin' || req.appUser?.organizationRole === 'admin';

        if (!isAdmin) {
            return res.status(403).json({ error: 'Apenas administradores podem editar movimentações.' });
        }

        const { date, quantity, notes } = req.body;

        const movement = await StockMovement.findOne({ _id: id, organizationId });
        if (!movement) return res.status(404).json({ error: 'Movimentação não encontrada.' });

        const item = await StockItem.findOne({ _id: movement.stockItemId, organizationId });
        if (!item) return res.status(404).json({ error: 'Item de estoque associado não encontrado.' });

        // Calculate Delta
        // If quantity changed, we need to adjust the item balance
        // Note: 'quantity' in movement is signed (+ for entry, - for consumption)
        // If the user edits a Consumption (-10) to (-15), the val passed in body might be absolute or signed?
        // Let's assume the frontend sends the SIGNED value consistent with the movement type?
        // Actually best to stick to specific logic: 
        // If movement type is ENTRY/ADJUSTMENT, quantity is usually positive (unless neg adjustment).
        // If CONSUMPTION, quantity is stored negative.
        // Let's expect the frontend to send the 'raw' new value.
        // Be careful: if frontend sends positive 10 for a consumption, we must flip it?
        // Let's assume frontend sends the value exactly as it should be stored.

        // HOWEVER, it's safer if we check type.
        const newQuantitySigned = Number(quantity);

        // Validation: Consumption should generally be negative, Entry positive.
        // But for flexibility let's just trust the arithmetic diff for now, 
        // but warn if sign flips unexpectedly?

        const oldQuantity = Number(movement.quantity);
        const quantityDiff = newQuantitySigned - oldQuantity;

        // Update Item
        const newStockLevel = Number(item.quantity) + quantityDiff;
        if (newStockLevel < 0) {
            return res.status(400).json({ error: 'A alteração resultaria em estoque negativo.' });
        }

        item.quantity = newStockLevel;
        await item.save();

        // Audit Log
        const typeMap: Record<string, string> = { ENTRY: 'ENTRADA', CONSUMPTION: 'CONSUMO', ADJUSTMENT: 'AJUSTE' };
        const typeLabel = typeMap[movement.type] || movement.type;

        await StockAuditLog.create({
            organizationId,
            stockItemId: item._id,
            movementId: movement._id,
            movementNumber: movement.movementNumber,
            userId,
            userName,
            action: 'UPDATE',
            details: `Edição de Movimentação (#${movement.movementNumber || '?'} ${typeLabel}): Qtd ${oldQuantity} -> ${newQuantitySigned}`,
            oldValues: { date: movement.date, quantity: movement.quantity, notes: movement.notes },
            newValues: { date, quantity: newQuantitySigned, notes }
        });

        // Update Movement
        movement.quantity = newQuantitySigned;
        if (date) movement.date = date;
        if (notes !== undefined) movement.notes = notes;
        await movement.save();

        res.json(movement);

    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        console.error('Error updating movement:', error);
        res.status(500).json({ error: message });
    }
};

export const deleteStockMovement = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const organizationId = req.appUser?.organizationId;
        const userName = req.appUser?.name || req.appUser?.email || 'Unknown User';
        const userId = req.appUser?.clerkId || 'system';
        const isAdmin = req.appUser?.role === 'admin' || req.appUser?.organizationRole === 'admin';

        if (!isAdmin) {
            return res.status(403).json({ error: 'Apenas administradores podem excluir movimentações.' });
        }

        const movement = await StockMovement.findOne({ _id: id, organizationId });
        if (!movement) return res.status(404).json({ error: 'Movimentação não encontrada.' });

        const item = await StockItem.findOne({ _id: movement.stockItemId, organizationId });
        if (!item) return res.status(404).json({ error: 'Item de estoque associado não encontrado.' });

        // Reverse the effect
        // If we delete an Entry (+10), we MUST subtract 10 from Item.
        // If we delete a Consumption (-10), we MUST add 10 (subtract -10) to Item.
        // So: Item.quantity -= movement.quantity

        const reverseQty = Number(movement.quantity);
        const newStockLevel = Number(item.quantity) - reverseQty;

        if (newStockLevel < 0) {
            return res.status(400).json({ error: 'A exclusão resultaria em estoque negativo.' });
        }

        item.quantity = newStockLevel;
        await item.save();

        // Audit Log
        const typeMap: Record<string, string> = { ENTRY: 'ENTRADA', CONSUMPTION: 'CONSUMO', ADJUSTMENT: 'AJUSTE' };
        const typeLabel = typeMap[movement.type] || movement.type;

        await StockAuditLog.create({
            organizationId,
            stockItemId: item._id,
            movementId: movement._id,
            movementNumber: movement.movementNumber,
            userId,
            userName,
            action: 'DELETE',
            details: `Exclusão de Movimentação (#${movement.movementNumber || '?'} ${typeLabel}): Qtd ${movement.quantity}`,
            oldValues: movement.toObject()
        });

        await StockMovement.deleteOne({ _id: id });

        res.status(204).send();

    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        console.error('Error deleting movement:', error);
        res.status(500).json({ error: message });
    }
};

export const getStockAuditLogs = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params; // StockItem ID
        const organizationId = req.appUser?.organizationId;

        const logs = await StockAuditLog.find({ stockItemId: id, organizationId })
            .sort({ timestamp: -1 });

        res.json(logs);
    } catch (error: unknown) {
        const message = error instanceof Error ? error.message : 'Unknown error';
        res.status(500).json({ error: message });
    }
};
