import fs from 'fs/promises';
import path from 'path';
import {Portfolio, PricePoint, Transaction, TransactionType} from '../types';
import {getDataDir} from '../utils/storageUtils';
import {getTransactions} from './transactionService';
import {getPriceHistory} from './priceHistoryService';
import {formatNumber} from '../utils/formatUtils';
import {Logger} from '../utils/logger';

function getPortfolioPath(): string {
    return path.resolve(getDataDir('portfolioService'), 'portfolio.json');
}

export async function getPortfolio(): Promise<Portfolio> {
    try {
        const data = await fs.readFile(getPortfolioPath(), 'utf-8');
        return JSON.parse(data) as Portfolio;
    } catch (error) {
        if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
            // File doesn't exist, create a default portfolio
            const defaultPortfolio: Portfolio = {
                assets: [],
                currencies: [],
                total: {
                    eur: 0
                }
            };
            
            // Ensure the directory exists
            await fs.mkdir(path.dirname(getPortfolioPath()), { recursive: true });
            
            // Save the default portfolio
            await savePortfolio(defaultPortfolio);
            Logger.info('Created new portfolio file with default structure');
            
            return defaultPortfolio;
        }
        
        // If it's any other error, log and exit
        Logger.error('Error reading portfolio summary', error);
        process.exit(1);
    }
}

export async function savePortfolio(portfolio: Portfolio): Promise<void> {
    try {
        await fs.writeFile(getPortfolioPath(), JSON.stringify(portfolio, null, 2));
    } catch (error) {
        Logger.error('Error updating portfolio summary', error);
        process.exit(1);
    }
}

function calculateMWR(cashFlows: Array<{ amount: number, date: Date }>): number {
    const guess = 0.1; // Initial guess of 10%
    const maxIterations = 100;
    const tolerance = 0.0000001;

    function npv(rate: number): number {
        return cashFlows.reduce((sum, flow) => {
            const timeInYears = (flow.date.getTime() - cashFlows[0].date.getTime()) / (365 * 24 * 60 * 60 * 1000);
            return sum + flow.amount / Math.pow(1 + rate, timeInYears);
        }, 0);
    }

    function npvDerivative(rate: number): number {
        return cashFlows.reduce((sum, flow) => {
            const timeInYears = (flow.date.getTime() - cashFlows[0].date.getTime()) / (365 * 24 * 60 * 60 * 1000);
            return sum - (timeInYears * flow.amount) / Math.pow(1 + rate, timeInYears + 1);
        }, 0);
    }

    let rate = guess;
    for (let i = 0; i < maxIterations; i++) {
        const currentNpv = npv(rate);
        if (Math.abs(currentNpv) < tolerance) {
            return rate;
        }
        rate = rate - currentNpv / npvDerivative(rate);
    }

    return rate;
}

function getStockMWR(transactions: Transaction[], currentValue: number, currentDate: Date): number {
    // Ignore vested stocks
    if (transactions.some(t => t.type.toUpperCase() === TransactionType.VESTED.toUpperCase())) {
        return 0;
    }

    // Convert transactions to cash flows
    const cashFlows = [
        ...transactions.map(t => ({
            amount: -(t.quantity * t.price),
            date: new Date(t.date)
        })),
        {
            amount: currentValue,
            date: currentDate
        }
    ];

    // Sort cash flows by date
    cashFlows.sort((a, b) => a.date.getTime() - b.date.getTime());

    return calculateMWR(cashFlows);
}

/**
 * Calculate Time-Weighted Return (TWR) for an asset
 * TWR eliminates the distorting effects of cash inflows and outflows
 * and provides a more accurate measure of investment performance
 * 
 * @param transactions Array of transactions for the asset
 * @param currentPrice Current price of the asset
 * @param currentDate Current date
 * @returns The time-weighted return as a decimal (e.g., 0.1 for 10%)
 */
export function calculateTWR(transactions: Transaction[], currentPrice: number, currentDate: Date): number {
    if (transactions.length === 0 || currentPrice <= 0) {
        return 0;
    }

    // Filter out non-buy/sell transactions as they don't affect holdings
    const relevantTransactions = transactions.filter(
        t => t.type === TransactionType.BUY || 
             t.type === TransactionType.SELL || 
             t.type === TransactionType.VESTED
    );

    if (relevantTransactions.length === 0) {
        return 0;
    }

    // Sort transactions by date
    const sortedTransactions = [...relevantTransactions].sort(
        (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
    );

    // Create a timeline of all events (transactions and final valuation)
    type TimelineEvent = {
        date: Date;
        isTransaction: boolean;
        transaction?: Transaction;
        price: number;
        holdings: number; // Number of shares held after this event
        value: number;    // Value of holdings after this event
    };

    const timeline: TimelineEvent[] = [];
    let currentHoldings = 0;

    // Add transactions to timeline
    for (const transaction of sortedTransactions) {
        // Get transaction date
        const transactionDate = new Date(transaction.date);
        
        if (transaction.type === TransactionType.BUY || transaction.type === TransactionType.VESTED) {
            currentHoldings += transaction.quantity;
        } else if (transaction.type === TransactionType.SELL) {
            currentHoldings -= transaction.quantity;
        }

        timeline.push({
            date: transactionDate,
            isTransaction: true,
            transaction,
            price: transaction.price, // Use transaction price
            holdings: currentHoldings,
            value: currentHoldings * transaction.price
        });
    }

    // Add the current price point to calculate final return
    timeline.push({
        date: currentDate,
        isTransaction: false,
        price: currentPrice,
        holdings: currentHoldings,
        value: currentHoldings * currentPrice
    });

    // Sort timeline by date
    timeline.sort((a, b) => a.date.getTime() - b.date.getTime());

    // Calculate holding period returns between cash flow events
    const holdingPeriodReturns: number[] = [];
    let previousValue = 0;
    let previousHoldings = 0;

    for (let i = 0; i < timeline.length; i++) {
        const event = timeline[i];
        
        if (i > 0) {
            // If holdings changed (due to a transaction), calculate return for the period
            if (event.isTransaction && event.transaction) {
                // Calculate value before transaction (using the price from this transaction)
                const valueBeforeTransaction = previousHoldings * event.price;
                
                // Calculate holding period return if we had previous holdings
                if (previousHoldings > 0 && previousValue > 0) {
                    // Calculate HPR: (End Value / Start Value) - 1
                    const hpr = valueBeforeTransaction / previousValue - 1;
                    
                    // Sanity check: Ignore extreme values that might be due to data issues
                    if (hpr > -0.9 && hpr < 10) {  // Allow up to 1000% gain but not more
                        holdingPeriodReturns.push(hpr);
                    } else {
                        Logger.debug(`Ignoring extreme HPR value: ${hpr} for transaction on ${event.date.toISOString()}`);
                    }
                }
                
                // Update previous values after transaction
                previousValue = event.value;
                previousHoldings = event.holdings;
            } else if (!event.isTransaction) {
                // For price updates (not transactions), calculate return if holdings didn't change
                if (previousHoldings > 0 && previousValue > 0 && event.holdings === previousHoldings) {
                    // Calculate HPR: (End Value / Start Value) - 1
                    const hpr = event.value / previousValue - 1;
                    
                    // Sanity check: Ignore extreme values that might be due to data issues
                    if (hpr > -0.9 && hpr < 10) {  // Allow up to 1000% gain but not more
                        holdingPeriodReturns.push(hpr);
                    } else {
                        Logger.debug(`Ignoring extreme HPR value: ${hpr} for price update on ${event.date.toISOString()}`);
                    }
                }
                
                // Update previous values
                previousValue = event.value;
                previousHoldings = event.holdings;
            }
        } else {
            // First event, just store the values
            previousValue = event.value;
            previousHoldings = event.holdings;
        }
    }

    // If no valid holding period returns, return 0
    if (holdingPeriodReturns.length === 0) {
        return 0;
    }

    // Calculate TWR by compounding the holding period returns
    const twr = holdingPeriodReturns.reduce((acc, hpr) => (1 + acc) * (1 + hpr) - 1, 0);
    
    // Apply a sanity check to the final TWR value
    if (twr < -0.9 || twr > 10) {
        Logger.debug(`Calculated TWR is outside reasonable range: ${twr}. Capping it.`);
        return twr < -0.9 ? -0.9 : 10;  // Cap at -90% loss or 1000% gain
    }
    
    return twr;
}

export async function updatePortfolio(): Promise<void> {
    try {
        const portfolio = await getPortfolio();

        for (const asset of portfolio.assets) {
            try {
                const transactions = await getTransactions(asset.symbol);
                const priceHistory = await getPriceHistory(asset.symbol);

                let totalQuantity = 0;
                let totalCost = 0;

                for (const transaction of transactions) {
                    if (transaction.type.toUpperCase() === TransactionType.BUY.toUpperCase() || transaction.type.toUpperCase() === TransactionType.VESTED.toUpperCase()) {
                        totalQuantity += transaction.quantity;
                        totalCost += transaction.quantity * transaction.price;
                    } else if (transaction.type.toUpperCase() === TransactionType.SELL.toUpperCase()) {
                        totalQuantity -= transaction.quantity;
                        totalCost -= (totalCost / totalQuantity) * transaction.quantity;
                    }
                }

                const avgCost = totalQuantity > 0 ? totalCost / totalQuantity : 0;

                // Get latest price date
                const latestPrice = priceHistory.length > 0 ? priceHistory
                    .sort((a: PricePoint, b: PricePoint) =>
                        new Date(b.date).getTime() - new Date(a.date).getTime()
                    )[0] : { price: 0, date: new Date().toISOString() };

                const mwr = getStockMWR(transactions, asset.currentValue, new Date());
                
                // Calculate Time-Weighted Return (TWR)
                const twr = calculateTWR(transactions, latestPrice?.price || 0, new Date(latestPrice?.date || new Date().toISOString()));

                // Update asset position
                asset.lastPrice = latestPrice?.price || 0;
                asset.quantity = totalQuantity;
                asset.totalCost = totalCost;
                asset.avgCost = avgCost;
                asset.currentValue = asset.quantity * asset.lastPrice;
                asset.profit = asset.currentValue - asset.totalCost;
                asset.profitPercentage = asset.totalCost > 0 ? (asset.profit / asset.totalCost) * 100 : 0;
                asset.lastUpdated = latestPrice.date;
                asset.mwr = mwr;
                asset.twr = twr;

                Logger.info(
                    `Updated ${asset.symbol}:\n\tvalue: ${formatNumber(asset.currentValue)}, ` +
                    `p&l: ${formatNumber(asset.profit)} (${asset.profitPercentage.toFixed(2)}%), ` +
                    `TWR: ${(twr * 100).toFixed(2)}%`
                );
            } catch (error) {
                Logger.error(`Error updating asset ${asset.symbol}`, error);
            }
        }

        const currencies = [...new Set(portfolio.assets.map(asset => asset.currency))];

        for (const currency of currencies) {
            const assets = portfolio.assets.filter(asset => asset.currency === currency);
            const value = assets.reduce((total, asset) => total + asset.currentValue, 0);
            const cost = assets.reduce((total, asset) => total + asset.totalCost, 0);

            const currencyPortfolio = {
                cost,
                value,
                profit: value - cost,
                profitPercentage: cost > 0 ? ((value - cost) / cost) * 100 : 0,
                exchangeRate: {
                    eur: 1
                },
                lastUpdated: new Date().toISOString()
            };

            portfolio.currencies = portfolio.currencies || [];
            const existingCurrencyPortfolio = portfolio.currencies.find(c => c.currency.toUpperCase() === currency.toUpperCase());

            if (existingCurrencyPortfolio) {
                Object.assign(existingCurrencyPortfolio, currencyPortfolio);
            } else {
                portfolio.currencies.push({
                    ...currencyPortfolio,
                    currency
                });
            }

            Logger.info(
                `Updated ${currency} portfolio: value ${formatNumber(currencyPortfolio.value)}`
            );

        }

        await savePortfolio(portfolio);

    } catch (error) {
        Logger.error('Error updating portfolio performance', error);
        process.exit(1);
    }
}


