import axios from 'axios';
import { Request, Response } from 'express';
import { messages } from "../../utills/common";
import { Transaction } from "../../database/transaction/transaction";
import { Notification } from "../../database/notification/notification";
import { User } from '../../database/user/user';
import { sendErrorResponse, sendSuccessResponse } from "../../utills/response";

import yenv from "yenv";
const env = yenv("env.yaml", { env: "development" });

const PAYONEER_API_BASE = env.PAYONEER_API_BASE;  // Set this to Payoneer's API base URL

// Function to get Payoneer Access Token
const getPayoneerAccessToken = async (): Promise<string> => {
    try {
        const response = await axios.post(`${PAYONEER_API_BASE}/v1/oauth2/token`, {
            grant_type: 'client_credentials',
            client_id: env.PAYONEER_CLIENT_ID,
            client_secret: env.PAYONEER_CLIENT_SECRET,
        });
        return response.data.access_token;
    } catch (error: any) {
        console.error('Error getting Payoneer access token:', error.response?.data || error.message);
        throw error;
    }
};

// Helper function to validate amount
const validateAmount = (amount: number, res: Response, errorMessage: string): boolean => {
    if (!amount || isNaN(Number(amount))) {
        sendErrorResponse(res, 400, errorMessage);
        return false;
    }
    return true;
};

// Helper function to find user by ID
const findUserById = async (userId: string, res: Response): Promise<User | null> => {
    const user = await User.findOne({ where: { id: userId } });
    if (!user) {
        sendErrorResponse(res, 404, 'User not found');
        return null;
    }
    return user;
};

// Helper function to calculate user balance
const calculateUserBalance = async (userId: string): Promise<{
    totalInvestedAmount: number;
    totalWithdrawAmount: number;
    remainingAmount: number;
}> => {
    const allInvestments = await Transaction.createQueryBuilder()
        .where({ userId })
        .getMany();

    const totalInvestedAmount = allInvestments.reduce((sum, investment) =>
        sum + investment.depositAmount, 0
    );
    const totalWithdrawAmount = allInvestments.reduce((sum, investment) =>
        sum + investment.withdrawAmount, 0
    );
    const remainingAmount = totalInvestedAmount - totalWithdrawAmount;

    return { totalInvestedAmount, totalWithdrawAmount, remainingAmount };
};

// Helper function to handle Payoneer errors
const handlePayoneerError = (error: any, res: Response, context: string): void => {
    console.error(`Error ${context}:`, error.response?.data ?? error.message);
    res.status(500).send(`Error ${context}`);
};

// Money Transfer API endpoint using Payoneer
export const transferToAdmin = async (req: Request, res: Response): Promise<any> => {
    try {
        const { transferAmount, userId } = req.body;

        // Validate the transfer request
        if (!validateAmount(transferAmount, res, 'Invalid transfer amount')) return;

        // Get the user Payoneer account details from the User table
        const user = await findUserById(userId, res);
        if (!user) return;

        // const { payoneerAccountId } = user; // Ensure `payoneerAccountId` exists in your User model
        const { email } = user;
        if (!email) {
            return sendErrorResponse(res, 400, 'User does not have a linked Payoneer account');
        }

        // Get Payoneer access token
        const accessToken = await getPayoneerAccessToken();

        // Create the Payoneer transfer request payload
        const transferData = {
            payee_id: env.ADMIN_PAYONEER_ACCOUNT_ID, // Admin's Payoneer account ID
            amount: transferAmount,
            currency: 'USD',
            description: 'Payment to Admin',
            client_reference_id: `transfer_${Date.now()}`,
            sender_payoneer_id: email,
        };

        // Initiate the transfer request to Payoneer
        const response = await axios.post(`${PAYONEER_API_BASE}/v4/programs/payouts`, transferData, {
            headers: {
                Authorization: `Bearer ${accessToken}`,
                'Content-Type': 'application/json',
            },
        });

        const latestTransaction = await Transaction.createQueryBuilder("transaction")
            .where("transaction.userId = :userId", { userId })
            .orderBy("transaction.created_at", "DESC")
            .getOne();

        // Calculate the total invested amount
        const totalInvestedAmount = latestTransaction
            ? latestTransaction.totalInvestedAmount + transferAmount // Add to previous total
            : transferAmount; // First transaction

        if (response.status === 201 || response.status === 200) {
            // Log the transfer in the Transaction table
            const transaction = await Transaction.create({
                userId,
                depositAmount: transferAmount, 
                totalInvestedAmount: totalInvestedAmount,
                // adminPayoneerAccountId: env.ADMIN_PAYONEER_ACCOUNT_ID,
                // status: 'COMPLETED',
                // transactionReference: response.data.payout_id,
            });
            await transaction.save();

            return sendSuccessResponse(res, 200, messages.transferSuccess, transaction);
        } else {
            return sendErrorResponse(res, 500, 'Failed to process transfer');
        }
    } catch (error: any) {
        handlePayoneerError(error, res, 'processing transfer');
    }
};

// Withdrawal API endpoint using Payoneer
export const withdrawToUser = async (req: Request, res: Response): Promise<any> => {
    try {
        const { withdrawAmount, userId, transactionId } = req.body;

        // Validate the withdrawal request
        if (!validateAmount(withdrawAmount, res, 'Invalid withdrawal amount')) return;

        // Get the user email and bank details from the User table
        const user = await findUserById(userId, res);
        if (!user) return;

        const { accountName, accountNumber, bsbNumber } = user;

        // Calculate total invested and withdrawn amounts
        const { remainingAmount } = await calculateUserBalance(userId);

        // Check if the withdrawal amount is less than or equal to the remaining available amount
        if (withdrawAmount > remainingAmount) {
            return sendErrorResponse(res, 400, messages.insufficientFunds);
        }

        // Proceed to Payoneer payout
        const accessToken = await getPayoneerAccessToken(); // Get Payoneer access token
        
        const payoutData = {
            sender_batch_header: {
                sender_batch_id: `batch_${Date.now()}`,
                email_subject: 'You have received a payment',
                email_message: 'You have received a payment from Admin.',
            },
            items: [
                {
                    recipient_type: 'BANK_ACCOUNT',
                    recipient_name: accountName,
                    account_number: accountNumber,
                    routing_number: bsbNumber,  // Australian BSB for routing (if applicable)
                    amount: {
                        value: withdrawAmount,
                        currency: 'USD',
                    },
                    note: 'Withdrawal payment',
                    sender_item_id: `item_${Date.now()}`,
                },
            ],
        };

        // Initiate the Payoneer payout request
        const response = await axios.post(`${PAYONEER_API_BASE}/v1/payments/payouts`, payoutData, {
            headers: {
                Authorization: `Bearer ${accessToken}`,
                'Content-Type': 'application/json',
            },
        });

        if (response.status === 201) {
            const existingTransaction = await Transaction.findOne({ where: { id: transactionId } });

            if (!existingTransaction) {
                return sendErrorResponse(res, 404, 'Transaction not found for this user');
            }

            // Update the necessary fields
            existingTransaction.withdrawAmount = withdrawAmount;
            existingTransaction.totalInvestedAmount = remainingAmount - withdrawAmount;
            existingTransaction.iswithdrawAmount = true;  // Mark it as a withdrawal
            
            // Save the updated record
            await existingTransaction.save();

            // Optionally, send a notification after successful withdrawal
            await Notification.create({
                userId,
                msg: messages.notificationWithdrawMessage,
            }).save();

            return sendSuccessResponse(res, 200, messages.withdrawSuccess, existingTransaction);
        } else {
            res.status(500).send('Failed to process payout');
        }
    } catch (error: any) {
        handlePayoneerError(error, res, 'processing withdrawal');
    }
};
