package service

import (
	"encoding/json"
	"fmt"
	"sort"
	"time"

	"portfolio-tracker/internal/model"
	"portfolio-tracker/internal/util"
)

// PositionCalculator calculates positions from activities
type PositionCalculator struct{}

// NewPositionCalculator creates a new position calculator
func NewPositionCalculator() *PositionCalculator {
	return &PositionCalculator{}
}

// CalculatePositions calculates all positions for a portfolio
func (pc *PositionCalculator) CalculatePositions(portfolio model.Portfolio) ([]model.Position, model.PositionSummary, error) {
	// Group activities by stock
	stockActivities := pc.groupActivitiesByStock(portfolio.Activities)

	var positions []model.Position
	var totalValue, totalCostBasis, totalUnrealizedPL, totalRealizedPL, totalDividends float64

	for stock, activities := range stockActivities {
		position, err := pc.calculateStockPosition(stock, activities, portfolio.BaseCurrency)
		if err != nil {
			fmt.Printf("Error calculating position for %s: %v\n", stock, err)
			continue
		}

		// Only include positions that have shares or have had activity
		if position.IsOpen() || position.TotalBought > 0 || position.TotalSold > 0 {
			positions = append(positions, position)

			// Add to portfolio totals
			totalValue += position.CurrentValue
			totalCostBasis += position.TotalCostBasis
			totalUnrealizedPL += position.UnrealizedPL
			totalRealizedPL += position.RealizedPL
			totalDividends += position.TotalDividends
		}
	}

	// Calculate total return
	totalReturn := totalUnrealizedPL + totalRealizedPL + totalDividends
	totalReturnPct := 0.0
	if totalCostBasis > 0 {
		totalReturnPct = (totalReturn / totalCostBasis) * 100
	}

	summary := model.PositionSummary{
		TotalValue:        totalValue,
		TotalCostBasis:    totalCostBasis,
		TotalUnrealizedPL: totalUnrealizedPL,
		TotalRealizedPL:   totalRealizedPL,
		TotalDividends:    totalDividends,
		TotalReturn:       totalReturn,
		TotalReturnPct:    totalReturnPct,
		Currency:          portfolio.BaseCurrency,
		LastUpdated:       time.Now(),
	}

	// Sort positions by current value (descending)
	sort.Slice(positions, func(i, j int) bool {
		return positions[i].CurrentValue > positions[j].CurrentValue
	})

	return positions, summary, nil
}

// groupActivitiesByStock groups activities by stock symbol
func (pc *PositionCalculator) groupActivitiesByStock(activities []model.Activity) map[string][]model.Activity {
	stockActivities := make(map[string][]model.Activity)

	for _, activity := range activities {
		stockActivities[activity.Stock] = append(stockActivities[activity.Stock], activity)
	}

	// Sort activities by date within each stock
	for stock := range stockActivities {
		sort.Slice(stockActivities[stock], func(i, j int) bool {
			return stockActivities[stock][i].Date.Before(stockActivities[stock][j].Date)
		})
	}

	return stockActivities
}

// calculateStockPosition calculates position for a single stock
func (pc *PositionCalculator) calculateStockPosition(stock string, activities []model.Activity, baseCurrency string) (model.Position, error) {
	position := model.Position{
		Stock:        stock,
		BaseCurrency: baseCurrency,
		LastUpdated:  time.Now(),
	}

	var totalShares float64
	var totalCostBasis float64
	var totalDividends float64
	var totalBought float64
	var totalSold float64
	var realizedPL float64

	// Process each activity
	for _, activity := range activities {
		// Handle GBp (pence) to GBP (pounds) conversion for historical transactions
		activityPrice := activity.Price
		activityCurrency := activity.Currency
		if activity.Currency == "GBp" {
			activityPrice = activity.Price / 100.0
			activityCurrency = "GBP"
		}

		convertedAmount, err := pc.convertToBaseCurrency(activity.Amount, activityCurrency, baseCurrency, activity.Date)
		if err != nil {
			fmt.Printf("Warning: Could not convert currency for %s: %v\n", stock, err)
			convertedAmount = activity.Amount // Use original amount as fallback
		}

		convertedPrice, err := pc.convertToBaseCurrency(activityPrice, activityCurrency, baseCurrency, activity.Date)
		if err != nil {
			fmt.Printf("Warning: Could not convert price for %s: %v\n", stock, err)
			convertedPrice = activity.Price // Use original price as fallback
		}

		convertedFee, err := pc.convertToBaseCurrency(activity.Fee, activityCurrency, baseCurrency, activity.Date)
		if err != nil {
			convertedFee = activity.Fee // Use original fee as fallback
		}

		convertedTaxes, err := pc.convertToBaseCurrency(activity.Taxes, activityCurrency, baseCurrency, activity.Date)
		if err != nil {
			convertedTaxes = activity.Taxes // Use original taxes as fallback
		}

		switch activity.Type {
		case model.Buy:
			totalShares += activity.Amount
			totalBought += activity.Amount
			totalCostBasis += (convertedPrice * activity.Amount) + convertedFee + convertedTaxes
			position.Currency = activityCurrency

		case model.Sell:
			if totalShares > 0 {
				// Calculate realized P&L using average cost method
				avgCostPerShare := totalCostBasis / totalShares
				soldValue := (convertedPrice * activity.Amount) - convertedFee - convertedTaxes
				soldCostBasis := avgCostPerShare * activity.Amount
				realizedPL += soldValue - soldCostBasis

				// Update totals
				totalShares -= activity.Amount
				totalSold += activity.Amount
				totalCostBasis -= soldCostBasis
			}

		case model.Dividend:
			totalDividends += convertedAmount

		case model.Fee:
			// Fees reduce the total return
			totalDividends -= convertedAmount

		case model.Tax:
			// Taxes reduce the total return
			totalDividends -= convertedAmount
		}
	}

	// Calculate average cost price
	averageCostPrice := 0.0
	if totalShares > 0 {
		averageCostPrice = totalCostBasis / totalShares
	}

	// Get current market price
	currentPrice, err := pc.getCurrentPrice(stock, baseCurrency)
	if err != nil {
		fmt.Printf("Warning: Could not get current price for %s: %v\n", stock, err)
		currentPrice = averageCostPrice // Use average cost as fallback
	}

	// Calculate current value and unrealized P&L
	currentValue := totalShares * currentPrice
	unrealizedPL := currentValue - totalCostBasis
	unrealizedPLPct := 0.0
	if totalCostBasis > 0 {
		unrealizedPLPct = (unrealizedPL / totalCostBasis) * 100
	}

	position.Shares = totalShares
	position.AverageCostPrice = averageCostPrice
	position.TotalCostBasis = totalCostBasis
	position.CurrentPrice = currentPrice
	position.CurrentValue = currentValue
	position.UnrealizedPL = unrealizedPL
	position.UnrealizedPLPct = unrealizedPLPct
	position.TotalDividends = totalDividends
	position.TotalBought = totalBought
	position.TotalSold = totalSold
	position.RealizedPL = realizedPL

	return position, nil
}

// convertToBaseCurrency converts amount to base currency
func (pc *PositionCalculator) convertToBaseCurrency(amount float64, fromCurrency, toCurrency string, date time.Time) (float64, error) {
	if fromCurrency == toCurrency {
		return amount, nil
	}

	return util.ConvertCurrencyHistorical(amount, fromCurrency, toCurrency, date)
}

// getCurrentPrice gets current market price for a stock
func (pc *PositionCalculator) getCurrentPrice(stock, baseCurrency string) (float64, error) {
	// Try to get current price from Yahoo Finance
	data, err := util.FetchYahooFinanceData(stock)
	if err != nil {
		return 0, fmt.Errorf("failed to fetch stock data: %v", err)
	}

	// Parse the JSON response
	var resp util.YahooChartResponse
	err = json.Unmarshal([]byte(data), &resp)
	if err != nil {
		return 0, fmt.Errorf("failed to parse stock data: %v", err)
	}

	if len(resp.Chart.Result) == 0 {
		return 0, fmt.Errorf("no data found for stock %s", stock)
	}

	result := resp.Chart.Result[0]

	// Get the latest price
	var latestPrice float64
	if len(result.Indicators.Quote) > 0 && len(result.Indicators.Quote[0].Close) > 0 {
		prices := result.Indicators.Quote[0].Close
		// Find the last non-null price
		for i := len(prices) - 1; i >= 0; i-- {
			if prices[i] != 0 {
				latestPrice = prices[i]
				break
			}
		}
	}

	if latestPrice == 0 {
		return 0, fmt.Errorf("no valid price found for stock %s", stock)
	}

	// Convert to base currency if needed
	stockCurrency := "USD" // Default to USD if not specified
	if result.Meta.Currency != "" {
		stockCurrency = result.Meta.Currency
	}

	// Handle GBp (pence) to GBP (pounds) conversion
	if stockCurrency == "GBp" {
		latestPrice = latestPrice / 100.0
		stockCurrency = "GBP"
	}

	if stockCurrency != baseCurrency {
		convertedPrice, err := util.ConvertCurrency(latestPrice, stockCurrency, baseCurrency)
		if err != nil {
			fmt.Printf("Warning: Could not convert price from %s to %s: %v\n", stockCurrency, baseCurrency, err)
			return latestPrice, nil // Return original price if conversion fails
		}
		return convertedPrice, nil
	}

	return latestPrice, nil
}

// CalculatePositionForStock calculates position for a specific stock
func (pc *PositionCalculator) CalculatePositionForStock(stock string, activities []model.Activity, baseCurrency string) (model.Position, error) {
	// Filter activities for this stock
	var stockActivities []model.Activity
	for _, activity := range activities {
		if activity.Stock == stock {
			stockActivities = append(stockActivities, activity)
		}
	}

	// Sort by date
	sort.Slice(stockActivities, func(i, j int) bool {
		return stockActivities[i].Date.Before(stockActivities[j].Date)
	})

	return pc.calculateStockPosition(stock, stockActivities, baseCurrency)
}
