import Foundation
import SwiftData
import Testing
@testable import Forji

@MainActor
struct PersistenceSaveTests {
    private static let saveFailure = NSError(
        domain: "PersistenceSaveTests",
        code: 1,
        userInfo: [NSLocalizedDescriptionKey: "the disk is full"],
    )

    private func makeContext() throws -> ModelContext {
        let container = try ModelContainer(
            for: ForgejoInstance.self,
            configurations: ModelConfiguration(isStoredInMemoryOnly: true),
        )
        return ModelContext(container)
    }

    private func storedInstances(in context: ModelContext) throws -> [ForgejoInstance] {
        try context.fetch(FetchDescriptor<ForgejoInstance>())
    }

    // MARK: - message

    @Test func messageIncludesTheUnderlyingError() {
        let message = PersistenceSave.message(for: Self.saveFailure)
        #expect(message.contains("the disk is full"))
    }

    // MARK: - success path

    @Test func successfulSaveReportsNoFailureAndPersists() throws {
        let context = try makeContext()
        context.insert(ForgejoInstance(serverURL: "https://a.com", username: "user", name: "Work"))

        #expect(context.saveOrRollback() == nil)
        #expect(try storedInstances(in: context).count == 1)
    }

    // MARK: - failure path

    @Test func failedSaveReportsTheFailure() throws {
        let context = try makeContext()

        let failure = context.saveOrRollback { throw Self.saveFailure }

        #expect(failure?.contains("the disk is full") == true)
    }

    @Test func failedSaveDiscardsAPendingInsert() throws {
        // The bug this guards: a failed save used to leave the inserted account in the
        // context, so the app showed it as connected until the next launch lost it.
        let context = try makeContext()
        context.insert(ForgejoInstance(serverURL: "https://a.com", username: "user", name: "Work"))

        #expect(context.saveOrRollback { throw Self.saveFailure } != nil)

        #expect(try storedInstances(in: context).isEmpty)
    }

    @Test func failedSaveRestoresAPendingDelete() throws {
        let context = try makeContext()
        context.insert(ForgejoInstance(serverURL: "https://a.com", username: "user", name: "Work"))
        try context.save()

        let instance = try #require(try storedInstances(in: context).first)
        context.delete(instance)

        #expect(context.saveOrRollback { throw Self.saveFailure } != nil)

        #expect(try storedInstances(in: context).count == 1)
    }

    @Test func failedSaveRestoresEditedValues() throws {
        let context = try makeContext()
        context.insert(ForgejoInstance(serverURL: "https://a.com", username: "user", name: "Work"))
        try context.save()

        let instance = try #require(try storedInstances(in: context).first)
        instance.name = "Renamed"
        instance.isDefault = true

        #expect(context.saveOrRollback { throw Self.saveFailure } != nil)

        let restored = try #require(try storedInstances(in: context).first)
        #expect(restored.name == "Work")
        #expect(!restored.isDefault)
    }
}
