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 PAYPAL_API_BASE = env.PAYPAL_MODE === 'live'
    ? env.PAYPAL_MODE_LIVE
    : env.PAYPAL_MODE_SANDBOX;

// Function to generate PayPal access token
const getAccessToken = async (): Promise<string> => {
    try {
        const response = await axios.post(`${PAYPAL_API_BASE}/v1/oauth2/token`, 'grant_type=client_credentials', {
            auth: {
                username: env.PAYPAL_CLIENT_KEY ?? '',
                password: env.PAYPAL_SECRET_KEY ?? '',
            },
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
        });
        return response.data.access_token;
    } catch (error: any) {
        console.error('Error getting PayPal 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))) {
        res.status(400).send(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 get latest transaction for user
const getLatestTransactionForUser = async (userId: string): Promise<Transaction | null> => {
    return await Transaction.createQueryBuilder("transaction")
        .where("transaction.userId = :userId", { userId })
        .orderBy("transaction.created_at", "DESC")
        .getOne();
};

// Helper function to calculate new total invested amount
const calculateNewTotalInvestedAmount = (
    latestTransaction: Transaction | null,
    amount: number
): number => {
    return latestTransaction
        ? latestTransaction.totalInvestedAmount + amount
        : amount;
};

// Helper function to update withdrawal transaction
const updateWithdrawalTransaction = async (
    transactionId: string,
    withdrawAmount: number,
    remainingAmount: number,
    res: Response
): Promise<Transaction | null> => {
    const existingTransaction = await Transaction.findOne({ where: { id: transactionId } });

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

    existingTransaction.withdrawAmount = withdrawAmount;
    existingTransaction.totalInvestedAmount = remainingAmount;
    existingTransaction.iswithdrawAmount = true;

    await existingTransaction.save();
    return existingTransaction;
};

// Helper function to handle PayPal errors
const handlePayPalError = (error: any, res: Response, context: string): void => {
    if (error.response?.status === 422) {
        res.status(400).send('Transaction failed: Insufficient funds');
    } else {
        console.error(`Error ${context}:`, error.response?.data ?? error.message);
        res.status(500).send(`Error ${context}`);
    }
};


// Create a PayPal Payment
export const payProduct = async (req: any, res: Response): Promise<void> => {
    const userId = req.user.id;
    const { amount } = req.body;

    if (!validateAmount(amount, res, 'Invalid amount')) return;

    try {
        const accessToken = await getAccessToken();

        const createPaymentJson = {
            intent: 'CAPTURE',
            purchase_units: [
                {
                    custom_id: userId,
                    amount: {
                        currency_code: 'USD',
                        value: amount,
                    },
                },
            ],
            application_context: {
                return_url: `${env.QUANT_URL}/auth/v1/success`,
                cancel_url: `${env.QUANT_URL}/auth/v1/cancel`,
            },
        };

        const response = await axios.post(`${PAYPAL_API_BASE}/v2/checkout/orders`, createPaymentJson, {
            headers: {
                Authorization: `Bearer ${accessToken}`,
                'Content-Type': 'application/json',
            },
        });

        const approvalUrl = response.data.links.find((link: any) => link.rel === 'approve')?.href;
        if (approvalUrl) {
            res.status(200).json({ approvalUrl });
        } else {
            res.status(500).send('Approval URL not found');
        }
    } catch (error: any) {
        handlePayPalError(error, res, 'processing payment');
    }
};

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 from the User table
        const user = await findUserById(userId, res);
        if (!user) return;

        const userEmail = user.email;

        // 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 PayPal payout
        const accessToken = await getAccessToken(); // Get PayPal 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: 'EMAIL',
                    receiver: userEmail,
                    amount: {
                        value: withdrawAmount,
                        currency: 'USD',
                    },
                    note: 'Withdrawal payment',
                    sender_item_id: `item_${Date.now()}`,
                },
            ],
        };

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

        if (response.status === 201) {
            const updatedTransaction = await updateWithdrawalTransaction(
                transactionId,
                withdrawAmount,
                remainingAmount,
                res
            );

            if (!updatedTransaction) return;

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

// Handle Success Page
export const successPage = async (req: Request, res: Response): Promise<void> => {
    try {
        const { token } = req.query;
        if (!token) {
            res.status(400).send('Invalid request');
            return;
        }

        // Get access token for PayPal API
        const accessToken = await getAccessToken();

        // Capture PayPal payment
        const response = await axios.post(`${PAYPAL_API_BASE}/v2/checkout/orders/${token}/capture`, {}, {
            headers: {
                Authorization: `Bearer ${accessToken}`,
                'Content-Type': 'application/json',
            },
        });

        // Check PayPal's response status
        const status = response.data.status;
        if (status !== 'COMPLETED') {
            res.status(400).send('Transaction failed: Payment not completed');
            return;
        }


        
        // Save the transaction to the database
        const userId = response.data.purchase_units[0].payments.captures[0].custom_id;
        const amount = parseFloat(response.data.purchase_units[0].payments.captures[0].amount.value);

        // Fetch the most recent transaction for the user, if any
        const latestTransaction = await getLatestTransactionForUser(userId);

        // Calculate the total invested amount
        const totalInvestedAmount = calculateNewTotalInvestedAmount(latestTransaction, amount);

        // Save the transaction to the database
        const newTransaction = await Transaction.create({
            userId: userId,
            depositAmount: amount, // PayPal amount
            totalInvestedAmount: totalInvestedAmount,
            isdepositAmount:true,
            created_at: new Date().toISOString(),
            updated_at: new Date().toISOString(),
        }).save();

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

        res.render('success', { details: response.data, newTransaction });
    } catch (error: any) {
        handlePayPalError(error, res, 'capturing payment');
    }
};


// Handle Cancel Page
export const cancelPage = async (req: Request, res: Response): Promise<void> => {
    try {
        res.render('cancel');
    } catch (error: any) {
        console.error(error.message);
    }
};