import XCTest
import CoreData
@testable import PortfolioJournal

// Note: These tests require Core Data model access from the test bundle.
// They are disabled by default. To run them, remove the XCTestCase subclass override.
// Run with: xcodebuild test -scheme PortfolioJournal -destination 'platform=iOS Simulator,name=iPhone 17'
@MainActor
final class GoalRepositoryTests: XCTestCase {

    override class var defaultTestSuite: XCTestSuite {
        // Skip all tests in this class - Core Data is not available in test bundle
        return XCTestSuite(name: "GoalRepositoryTests (Skipped - Core Data not available)")
    }

    var testStack: TestCoreDataStack?
    var sut: GoalRepository?

    override func setUp() async throws {
        try await super.setUp()
        do {
            testStack = try TestCoreDataStack()
            sut = GoalRepository(context: testStack!.viewContext)
        } catch {
            // Core Data setup failed - tests will be skipped
            testStack = nil
            sut = nil
        }
    }

    override func tearDown() async throws {
        testStack?.reset()
        testStack = nil
        sut = nil
        try await super.tearDown()
    }

    /// Helper to skip test if Core Data is not available
    private func requireCoreData() throws -> (TestCoreDataStack, GoalRepository) {
        guard let stack = testStack, let repository = sut else {
            throw XCTSkip("Core Data is not available in this test environment")
        }
        return (stack, repository)
    }

    // MARK: - Create Tests

    func testCreateGoal_withValidData_createsGoal() async throws {
        // Given
        let (_, repository) = try requireCoreData()
        let name = "Test Goal"
        let targetAmount: Decimal = 100_000

        // When
        repository.createGoal(
            name: name,
            targetAmount: targetAmount,
            targetDate: nil,
            account: nil
        )

        // Then
        repository.fetchGoals()
        XCTAssertEqual(repository.goals.count, 1)
        XCTAssertEqual(repository.goals.first?.name, name)
        XCTAssertEqual(repository.goals.first?.targetDecimal, targetAmount)
    }

    func testCreateGoal_withTargetDate_setsDate() async throws {
        // Given
        let (_, repository) = try requireCoreData()
        let targetDate = Date().adding(years: 1)

        // When
        repository.createGoal(
            name: "Dated Goal",
            targetAmount: 50_000,
            targetDate: targetDate,
            account: nil
        )

        // Then
        repository.fetchGoals()
        XCTAssertNotNil(repository.goals.first?.targetDate)
    }

    func testCreateGoal_withAccount_associatesAccount() async throws {
        // Given
        let (stack, repository) = try requireCoreData()
        let account = stack.createTestAccount(name: "Test Account")
        try? stack.save()

        // When
        repository.createGoal(
            name: "Account Goal",
            targetAmount: 75_000,
            targetDate: nil,
            account: account
        )

        // Then
        repository.fetchGoals()
        XCTAssertEqual(repository.goals.first?.account?.name, "Test Account")
    }

    // MARK: - Fetch Tests

    func testFetchGoals_withNoGoals_returnsEmpty() async throws {
        // Given
        let (_, repository) = try requireCoreData()

        // When
        repository.fetchGoals()

        // Then
        XCTAssertTrue(repository.goals.isEmpty)
    }

    func testFetchGoals_withMultipleGoals_returnsAll() async throws {
        // Given
        let (_, repository) = try requireCoreData()
        repository.createGoal(name: "Goal 1", targetAmount: 10_000, targetDate: nil, account: nil)
        repository.createGoal(name: "Goal 2", targetAmount: 20_000, targetDate: nil, account: nil)
        repository.createGoal(name: "Goal 3", targetAmount: 30_000, targetDate: nil, account: nil)

        // When
        repository.fetchGoals()

        // Then
        XCTAssertEqual(repository.goals.count, 3)
    }

    func testFetchGoals_forAccount_filtersCorrectly() async throws {
        // Given
        let (stack, repository) = try requireCoreData()
        let account1 = stack.createTestAccount(name: "Account 1")
        let account2 = stack.createTestAccount(name: "Account 2")
        try? stack.save()

        repository.createGoal(name: "Goal A1", targetAmount: 10_000, targetDate: nil, account: account1)
        repository.createGoal(name: "Goal A2", targetAmount: 20_000, targetDate: nil, account: account1)
        repository.createGoal(name: "Goal B1", targetAmount: 30_000, targetDate: nil, account: account2)

        // When
        repository.fetchGoals(for: account1)

        // Then
        XCTAssertEqual(repository.goals.count, 2)
        XCTAssertTrue(repository.goals.allSatisfy { $0.account?.name == "Account 1" })
    }

    // MARK: - Update Tests

    func testUpdateGoal_changesName() async throws {
        // Given
        let (_, repository) = try requireCoreData()
        repository.createGoal(name: "Original Name", targetAmount: 50_000, targetDate: nil, account: nil)
        repository.fetchGoals()
        let goal = repository.goals.first!

        // When
        repository.updateGoal(
            goal,
            name: "Updated Name",
            targetAmount: 50_000,
            targetDate: nil,
            clearTargetDate: false
        )

        // Then
        repository.fetchGoals()
        XCTAssertEqual(repository.goals.first?.name, "Updated Name")
    }

    func testUpdateGoal_changesTargetAmount() async throws {
        // Given
        let (_, repository) = try requireCoreData()
        repository.createGoal(name: "Test Goal", targetAmount: 50_000, targetDate: nil, account: nil)
        repository.fetchGoals()
        let goal = repository.goals.first!

        // When
        repository.updateGoal(
            goal,
            name: "Test Goal",
            targetAmount: 75_000,
            targetDate: nil,
            clearTargetDate: false
        )

        // Then
        repository.fetchGoals()
        XCTAssertEqual(repository.goals.first?.targetDecimal, 75_000)
    }

    func testUpdateGoal_clearTargetDate_removesDate() async throws {
        // Given
        let (_, repository) = try requireCoreData()
        let targetDate = Date().adding(years: 1)
        repository.createGoal(name: "Dated Goal", targetAmount: 50_000, targetDate: targetDate, account: nil)
        repository.fetchGoals()
        let goal = repository.goals.first!

        // When
        repository.updateGoal(
            goal,
            name: "Dated Goal",
            targetAmount: 50_000,
            targetDate: nil,
            clearTargetDate: true
        )

        // Then
        repository.fetchGoals()
        XCTAssertNil(repository.goals.first?.targetDate)
    }

    // MARK: - Delete Tests

    func testDeleteGoal_removesGoal() async throws {
        // Given
        let (_, repository) = try requireCoreData()
        repository.createGoal(name: "To Delete", targetAmount: 10_000, targetDate: nil, account: nil)
        repository.fetchGoals()
        let goal = repository.goals.first!

        // When
        repository.deleteGoal(goal)

        // Then
        repository.fetchGoals()
        XCTAssertTrue(repository.goals.isEmpty)
    }

    func testDeleteGoal_onlyDeletesSpecifiedGoal() async throws {
        // Given
        let (_, repository) = try requireCoreData()
        repository.createGoal(name: "Keep", targetAmount: 10_000, targetDate: nil, account: nil)
        repository.createGoal(name: "Delete", targetAmount: 20_000, targetDate: nil, account: nil)
        repository.fetchGoals()
        let goalToDelete = repository.goals.first { $0.name == "Delete" }!

        // When
        repository.deleteGoal(goalToDelete)

        // Then
        repository.fetchGoals()
        XCTAssertEqual(repository.goals.count, 1)
        XCTAssertEqual(repository.goals.first?.name, "Keep")
    }
}
