import XCTest
@testable import PortfolioJournal

final class DateExtensionsTests: XCTestCase {

    // MARK: - Start of Day

    func testStartOfDay_returnsDateAtMidnight() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        components.hour = 14
        components.minute = 30
        let date = Calendar.current.date(from: components)!

        // When
        let result = date.startOfDay

        // Then
        let resultComponents = Calendar.current.dateComponents([.hour, .minute, .second], from: result)
        XCTAssertEqual(resultComponents.hour, 0)
        XCTAssertEqual(resultComponents.minute, 0)
        XCTAssertEqual(resultComponents.second, 0)
    }

    // MARK: - Start of Month

    func testStartOfMonth_returnsFirstDayOfMonth() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        let date = Calendar.current.date(from: components)!

        // When
        let result = date.startOfMonth

        // Then
        let resultComponents = Calendar.current.dateComponents([.day, .month, .year], from: result)
        XCTAssertEqual(resultComponents.day, 1)
        XCTAssertEqual(resultComponents.month, 6)
        XCTAssertEqual(resultComponents.year, 2024)
    }

    // MARK: - Start of Year

    func testStartOfYear_returnsJanuaryFirst() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        let date = Calendar.current.date(from: components)!

        // When
        let result = date.startOfYear

        // Then
        let resultComponents = Calendar.current.dateComponents([.day, .month, .year], from: result)
        XCTAssertEqual(resultComponents.day, 1)
        XCTAssertEqual(resultComponents.month, 1)
        XCTAssertEqual(resultComponents.year, 2024)
    }

    // MARK: - End of Month

    func testEndOfMonth_returnsLastDayOfMonth() {
        // Given: June has 30 days
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        let date = Calendar.current.date(from: components)!

        // When
        let result = date.endOfMonth

        // Then
        let resultComponents = Calendar.current.dateComponents([.day, .month], from: result)
        XCTAssertEqual(resultComponents.day, 30)
        XCTAssertEqual(resultComponents.month, 6)
    }

    func testEndOfMonth_handlesFebruaryInLeapYear() {
        // Given: February 2024 (leap year) has 29 days
        var components = DateComponents()
        components.year = 2024
        components.month = 2
        components.day = 10
        let date = Calendar.current.date(from: components)!

        // When
        let result = date.endOfMonth

        // Then
        let resultComponents = Calendar.current.dateComponents([.day, .month], from: result)
        XCTAssertEqual(resultComponents.day, 29)
        XCTAssertEqual(resultComponents.month, 2)
    }

    // MARK: - Same Day Comparison

    func testIsSameDay_withSameDay_returnsTrue() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        components.hour = 10
        let date1 = Calendar.current.date(from: components)!

        components.hour = 20
        let date2 = Calendar.current.date(from: components)!

        // When/Then
        XCTAssertTrue(date1.isSameDay(as: date2))
    }

    func testIsSameDay_withDifferentDays_returnsFalse() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        let date1 = Calendar.current.date(from: components)!

        components.day = 16
        let date2 = Calendar.current.date(from: components)!

        // When/Then
        XCTAssertFalse(date1.isSameDay(as: date2))
    }

    // MARK: - Same Month Comparison

    func testIsSameMonth_withSameMonth_returnsTrue() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 1
        let date1 = Calendar.current.date(from: components)!

        components.day = 30
        let date2 = Calendar.current.date(from: components)!

        // When/Then
        XCTAssertTrue(date1.isSameMonth(as: date2))
    }

    func testIsSameMonth_withDifferentMonths_returnsFalse() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        let date1 = Calendar.current.date(from: components)!

        components.month = 7
        let date2 = Calendar.current.date(from: components)!

        // When/Then
        XCTAssertFalse(date1.isSameMonth(as: date2))
    }

    // MARK: - Same Year Comparison

    func testIsSameYear_withSameYear_returnsTrue() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 1
        let date1 = Calendar.current.date(from: components)!

        components.month = 12
        let date2 = Calendar.current.date(from: components)!

        // When/Then
        XCTAssertTrue(date1.isSameYear(as: date2))
    }

    func testIsSameYear_withDifferentYears_returnsFalse() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        let date1 = Calendar.current.date(from: components)!

        components.year = 2023
        let date2 = Calendar.current.date(from: components)!

        // When/Then
        XCTAssertFalse(date1.isSameYear(as: date2))
    }

    // MARK: - Adding Time

    func testAddingDays_addsCorrectly() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        let date = Calendar.current.date(from: components)!

        // When
        let result = date.adding(days: 10)

        // Then
        let resultComponents = Calendar.current.dateComponents([.day, .month], from: result)
        XCTAssertEqual(resultComponents.day, 25)
    }

    func testAddingDays_handlesNegativeValues() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        let date = Calendar.current.date(from: components)!

        // When
        let result = date.adding(days: -10)

        // Then
        let resultComponents = Calendar.current.dateComponents([.day, .month], from: result)
        XCTAssertEqual(resultComponents.day, 5)
    }

    func testAddingMonths_addsCorrectly() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        let date = Calendar.current.date(from: components)!

        // When
        let result = date.adding(months: 3)

        // Then
        let resultComponents = Calendar.current.dateComponents([.month, .year], from: result)
        XCTAssertEqual(resultComponents.month, 9)
        XCTAssertEqual(resultComponents.year, 2024)
    }

    func testAddingYears_addsCorrectly() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        let date = Calendar.current.date(from: components)!

        // When
        let result = date.adding(years: 2)

        // Then
        let resultComponents = Calendar.current.dateComponents([.year], from: result)
        XCTAssertEqual(resultComponents.year, 2026)
    }

    // MARK: - Days Between

    func testDaysBetween_calculatesCorrectly() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 1
        let startDate = Calendar.current.date(from: components)!

        components.day = 15
        let endDate = Calendar.current.date(from: components)!

        // When
        let result = startDate.daysBetween(endDate)

        // Then
        XCTAssertEqual(result, 14)
    }

    func testDaysBetween_withReversedDates_returnsNegative() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 15
        let startDate = Calendar.current.date(from: components)!

        components.day = 1
        let endDate = Calendar.current.date(from: components)!

        // When
        let result = startDate.daysBetween(endDate)

        // Then
        XCTAssertEqual(result, -14)
    }

    // MARK: - Months Between

    func testMonthsBetween_calculatesCorrectly() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 1
        components.day = 15
        let startDate = Calendar.current.date(from: components)!

        components.month = 6
        let endDate = Calendar.current.date(from: components)!

        // When
        let result = startDate.monthsBetween(endDate)

        // Then
        XCTAssertEqual(result, 5)
    }

    // MARK: - Years Between

    func testYearsBetween_calculatesCorrectly() {
        // Given
        var components = DateComponents()
        components.year = 2020
        components.month = 1
        components.day = 1
        let startDate = Calendar.current.date(from: components)!

        components.year = 2024
        let endDate = Calendar.current.date(from: components)!

        // When
        let result = startDate.yearsBetween(endDate)

        // Then
        XCTAssertEqual(result, 4.0, accuracy: 0.1)
    }

    // MARK: - DateRange Tests

    func testDateRange_contains_withDateInRange_returnsTrue() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 1
        let start = Calendar.current.date(from: components)!

        components.day = 30
        let end = Calendar.current.date(from: components)!

        let range = DateRange(start: start, end: end)

        components.day = 15
        let testDate = Calendar.current.date(from: components)!

        // When/Then
        XCTAssertTrue(range.contains(testDate))
    }

    func testDateRange_contains_withDateOutsideRange_returnsFalse() {
        // Given
        var components = DateComponents()
        components.year = 2024
        components.month = 6
        components.day = 1
        let start = Calendar.current.date(from: components)!

        components.day = 30
        let end = Calendar.current.date(from: components)!

        let range = DateRange(start: start, end: end)

        components.month = 7
        components.day = 15
        let testDate = Calendar.current.date(from: components)!

        // When/Then
        XCTAssertFalse(range.contains(testDate))
    }

    func testDateRange_lastMonths_createsCorrectRange() {
        // Given/When
        let range = DateRange.last(months: 3)

        // Then
        let monthsDiff = range.start.monthsBetween(range.end)
        XCTAssertEqual(monthsDiff, 3)
    }
}
