import XCTest
@testable import PortfolioJournal

final class CalculationServiceTests: XCTestCase {

    var sut: CalculationService!

    override func setUp() {
        super.setUp()
        sut = CalculationService.shared
        sut.invalidateCache()
    }

    override func tearDown() {
        sut = nil
        super.tearDown()
    }

    // MARK: - CAGR Tests

    func testCalculateCAGR_withValidInputs_returnsCorrectValue() {
        // Given: 10,000 growing to 15,000 over 3 years
        let startValue: Decimal = 10_000
        let endValue: Decimal = 15_000
        let startDate = Calendar.current.date(byAdding: .year, value: -3, to: Date())!
        let endDate = Date()

        // When
        let cagr = sut.calculateCAGR(
            startValue: startValue,
            endValue: endValue,
            startDate: startDate,
            endDate: endDate
        )

        // Then: CAGR should be approximately 14.47%
        // Formula: (15000/10000)^(1/3) - 1 = 0.1447
        XCTAssertEqual(cagr, 14.47, accuracy: 0.5)
    }

    func testCalculateCAGR_withZeroStartValue_returnsZero() {
        // Given
        let startValue: Decimal = 0
        let endValue: Decimal = 15_000
        let startDate = Calendar.current.date(byAdding: .year, value: -3, to: Date())!
        let endDate = Date()

        // When
        let cagr = sut.calculateCAGR(
            startValue: startValue,
            endValue: endValue,
            startDate: startDate,
            endDate: endDate
        )

        // Then
        XCTAssertEqual(cagr, 0)
    }

    func testCalculateCAGR_withSameDate_returnsZero() {
        // Given
        let startValue: Decimal = 10_000
        let endValue: Decimal = 15_000
        let date = Date()

        // When
        let cagr = sut.calculateCAGR(
            startValue: startValue,
            endValue: endValue,
            startDate: date,
            endDate: date
        )

        // Then
        XCTAssertEqual(cagr, 0)
    }

    func testCalculateCAGR_withNegativeReturn_returnsNegativeValue() {
        // Given: 10,000 declining to 8,000 over 2 years
        let startValue: Decimal = 10_000
        let endValue: Decimal = 8_000
        let startDate = Calendar.current.date(byAdding: .year, value: -2, to: Date())!
        let endDate = Date()

        // When
        let cagr = sut.calculateCAGR(
            startValue: startValue,
            endValue: endValue,
            startDate: startDate,
            endDate: endDate
        )

        // Then: CAGR should be approximately -10.56%
        XCTAssertLessThan(cagr, 0)
        XCTAssertEqual(cagr, -10.56, accuracy: 0.5)
    }

    func testCalculateCAGR_with100PercentGrowthOver1Year_returns100Percent() {
        // Given: 10,000 doubling to 20,000 in 1 year
        let startValue: Decimal = 10_000
        let endValue: Decimal = 20_000
        let startDate = Calendar.current.date(byAdding: .year, value: -1, to: Date())!
        let endDate = Date()

        // When
        let cagr = sut.calculateCAGR(
            startValue: startValue,
            endValue: endValue,
            startDate: startDate,
            endDate: endDate
        )

        // Then
        XCTAssertEqual(cagr, 100, accuracy: 1.0)
    }

    // MARK: - Max Drawdown Tests

    func testCalculateMaxDrawdown_withIncreasingValues_returnsZero() {
        // Given
        let values: [Decimal] = [100, 110, 120, 130, 140, 150]

        // When
        let maxDrawdown = sut.calculateMaxDrawdown(values: values)

        // Then
        XCTAssertEqual(maxDrawdown, 0)
    }

    func testCalculateMaxDrawdown_withDecline_returnsCorrectValue() {
        // Given: Peak at 100, drops to 80 (20% drawdown)
        let values: [Decimal] = [80, 100, 95, 80, 90]

        // When
        let maxDrawdown = sut.calculateMaxDrawdown(values: values)

        // Then
        XCTAssertEqual(maxDrawdown, 20, accuracy: 0.1)
    }

    func testCalculateMaxDrawdown_withMultipleDrawdowns_returnsLargest() {
        // Given: First drawdown 10%, second drawdown 25%
        let values: [Decimal] = [100, 90, 95, 120, 90, 110]

        // When
        let maxDrawdown = sut.calculateMaxDrawdown(values: values)

        // Then: 25% is the max drawdown (120 to 90)
        XCTAssertEqual(maxDrawdown, 25, accuracy: 0.1)
    }

    func testCalculateMaxDrawdown_withEmptyArray_returnsZero() {
        // Given
        let values: [Decimal] = []

        // When
        let maxDrawdown = sut.calculateMaxDrawdown(values: values)

        // Then
        XCTAssertEqual(maxDrawdown, 0)
    }

    func testCalculateMaxDrawdown_withSingleValue_returnsZero() {
        // Given
        let values: [Decimal] = [100]

        // When
        let maxDrawdown = sut.calculateMaxDrawdown(values: values)

        // Then
        XCTAssertEqual(maxDrawdown, 0)
    }

    // MARK: - Sharpe Ratio Tests

    func testCalculateSharpeRatio_withPositiveReturn_returnsPositiveValue() {
        // Given
        let averageReturn = 1.5 // 1.5% monthly return
        let volatility = 3.0    // 3% volatility

        // When
        let sharpeRatio = sut.calculateSharpeRatio(
            averageReturn: averageReturn,
            volatility: volatility
        )

        // Then
        XCTAssertGreaterThan(sharpeRatio, 0)
    }

    func testCalculateSharpeRatio_withZeroVolatility_returnsZero() {
        // Given
        let averageReturn = 1.5
        let volatility = 0.0

        // When
        let sharpeRatio = sut.calculateSharpeRatio(
            averageReturn: averageReturn,
            volatility: volatility
        )

        // Then
        XCTAssertEqual(sharpeRatio, 0)
    }

    func testCalculateSharpeRatio_withNegativeReturn_returnsNegativeValue() {
        // Given
        let averageReturn = -1.0 // -1% monthly return (below risk-free rate)
        let volatility = 5.0

        // When
        let sharpeRatio = sut.calculateSharpeRatio(
            averageReturn: averageReturn,
            volatility: volatility
        )

        // Then
        XCTAssertLessThan(sharpeRatio, 0)
    }

    // MARK: - Win Rate Tests

    func testCalculateWinRate_withAllPositiveMonths_returns100Percent() {
        // Given
        let monthlyReturns = [
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 5.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 3.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 2.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 8.0)
        ]

        // When
        let winRate = sut.calculateWinRate(monthlyReturns: monthlyReturns)

        // Then
        XCTAssertEqual(winRate, 100)
    }

    func testCalculateWinRate_withAllNegativeMonths_returnsZero() {
        // Given
        let monthlyReturns = [
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: -5.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: -3.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: -2.0)
        ]

        // When
        let winRate = sut.calculateWinRate(monthlyReturns: monthlyReturns)

        // Then
        XCTAssertEqual(winRate, 0)
    }

    func testCalculateWinRate_withMixedMonths_returnsCorrectPercentage() {
        // Given: 3 positive, 1 negative = 75% win rate
        let monthlyReturns = [
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 5.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: -3.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 2.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 1.0)
        ]

        // When
        let winRate = sut.calculateWinRate(monthlyReturns: monthlyReturns)

        // Then
        XCTAssertEqual(winRate, 75)
    }

    func testCalculateWinRate_withEmptyArray_returnsZero() {
        // Given
        let monthlyReturns: [InvestmentMetrics.MonthlyReturn] = []

        // When
        let winRate = sut.calculateWinRate(monthlyReturns: monthlyReturns)

        // Then
        XCTAssertEqual(winRate, 0)
    }

    // MARK: - Volatility Tests

    func testCalculateVolatility_withStableReturns_returnsLowVolatility() {
        // Given: Very stable returns
        let monthlyReturns = [
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 1.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 1.1),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 0.9),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 1.0)
        ]

        // When
        let volatility = sut.calculateVolatility(monthlyReturns: monthlyReturns)

        // Then
        XCTAssertLessThan(volatility, 1.0) // Very low volatility
    }

    func testCalculateVolatility_withVolatileReturns_returnsHighVolatility() {
        // Given: Very volatile returns
        let monthlyReturns = [
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 10.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: -8.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 15.0),
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: -12.0)
        ]

        // When
        let volatility = sut.calculateVolatility(monthlyReturns: monthlyReturns)

        // Then
        XCTAssertGreaterThan(volatility, 30.0) // High volatility
    }

    func testCalculateVolatility_withSingleMonth_returnsZero() {
        // Given
        let monthlyReturns = [
            InvestmentMetrics.MonthlyReturn(date: Date(), returnPercentage: 5.0)
        ]

        // When
        let volatility = sut.calculateVolatility(monthlyReturns: monthlyReturns)

        // Then
        XCTAssertEqual(volatility, 0)
    }

    // MARK: - Array Average Extension Tests

    func testArrayAverage_withValues_returnsCorrectAverage() {
        // Given
        let values = [10.0, 20.0, 30.0, 40.0]

        // When
        let average = values.average()

        // Then
        XCTAssertEqual(average, 25.0)
    }

    func testArrayAverage_withEmptyArray_returnsZero() {
        // Given
        let values: [Double] = []

        // When
        let average = values.average()

        // Then
        XCTAssertEqual(average, 0)
    }

    func testArrayAverage_withNegativeValues_returnsCorrectAverage() {
        // Given
        let values = [-10.0, 10.0, -5.0, 5.0]

        // When
        let average = values.average()

        // Then
        XCTAssertEqual(average, 0)
    }
}

// MARK: - Performance Tests

extension CalculationServiceTests {

    func testCalculateMaxDrawdown_performance() {
        // Given: Large dataset
        let values: [Decimal] = (0..<10000).map { Decimal($0 % 100 + 50) }

        // When/Then
        measure {
            _ = sut.calculateMaxDrawdown(values: values)
        }
    }

    func testCalculateVolatility_performance() {
        // Given: Large dataset
        let monthlyReturns = (0..<120).map { index in
            InvestmentMetrics.MonthlyReturn(
                date: Calendar.current.date(byAdding: .month, value: -index, to: Date())!,
                returnPercentage: Double.random(in: -10...10)
            )
        }

        // When/Then
        measure {
            _ = sut.calculateVolatility(monthlyReturns: monthlyReturns)
        }
    }
}
