import Testing
@testable import bookstax
// Note: ShareViewModel and its dependencies live in BookStaxShareExtension,
// which is a separate target. These tests are compiled against the extension
// module. If you build the tests via the main scheme, add BookStaxShareExtension
// sources to the bookstaxTests target membership as needed.
//
// The tests below use a MockShareAPIService injected at init time so no
// network or Keychain access is required.

// MARK: - Mock

final class MockShareAPIService: ShareAPIServiceProtocol, @unchecked Sendable {

    var shelvesToReturn:  [ShelfSummary]   = []
    var booksToReturn:    [BookSummary]    = []
    var chaptersToReturn: [ChapterSummary] = []
    var errorToThrow:     Error?

    func fetchShelves() async throws -> [ShelfSummary] {
        if let error = errorToThrow { throw error }
        return shelvesToReturn
    }

    func fetchBooks(shelfId: Int) async throws -> [BookSummary] {
        if let error = errorToThrow { throw error }
        return booksToReturn
    }

    func fetchChapters(bookId: Int) async throws -> [ChapterSummary] {
        if let error = errorToThrow { throw error }
        return chaptersToReturn
    }

    func createPage(bookId: Int, chapterId: Int?, title: String, markdown: String) async throws -> PageResult {
        if let error = errorToThrow { throw error }
        return PageResult(id: 42, name: title)
    }
}

// MARK: - Tests

@Suite("ShareViewModel")
@MainActor
struct ShareViewModelTests {

    // Isolated UserDefaults suite so tests don't pollute each other.
    private func makeDefaults() -> UserDefaults {
        let suiteName = "test.bookstax.shareext.\(UUID().uuidString)"
        let defaults  = UserDefaults(suiteName: suiteName)!
        defaults.removePersistentDomain(forName: suiteName)
        return defaults
    }

    // MARK: - 1. Shelves are loaded on start

    @Test("Beim Start werden alle Regale geladen")
    func shelvesLoadOnStart() async throws {
        let mock = MockShareAPIService()
        mock.shelvesToReturn = [
            ShelfSummary(id: 1, name: "Regal A", slug: "a"),
            ShelfSummary(id: 2, name: "Regal B", slug: "b")
        ]
        let vm = ShareViewModel(
            sharedText: "Testinhalt",
            apiService: mock,
            defaults:   makeDefaults()
        )

        await vm.loadShelves()

        #expect(vm.shelves.count == 2)
        #expect(vm.shelves[0].name == "Regal A")
        #expect(vm.shelves[1].name == "Regal B")
        #expect(vm.isLoading == false)
        #expect(vm.errorMessage == nil)
    }

    // MARK: - 2. Selecting a shelf loads its books

    @Test("Shelf-Auswahl lädt Bücher nach")
    func selectingShelfLoadsBooksAsync() async throws {
        let mock = MockShareAPIService()
        mock.shelvesToReturn = [ShelfSummary(id: 10, name: "Regal X", slug: "x")]
        mock.booksToReturn   = [
            BookSummary(id: 100, name: "Buch 1", slug: "b1"),
            BookSummary(id: 101, name: "Buch 2", slug: "b2")
        ]
        let vm = ShareViewModel(
            sharedText: "Test",
            apiService: mock,
            defaults:   makeDefaults()
        )

        await vm.selectShelf(ShelfSummary(id: 10, name: "Regal X", slug: "x"))

        #expect(vm.selectedShelf?.id == 10)
        #expect(vm.books.count == 2)
        #expect(vm.books[0].name == "Buch 1")
        #expect(vm.isLoading == false)
    }

    // MARK: - 3. Last shelf / book are restored from UserDefaults

    @Test("Gespeicherte Shelf- und Book-IDs werden beim Start wiederhergestellt")
    func lastSelectionIsRestored() async throws {
        let mock = MockShareAPIService()
        mock.shelvesToReturn = [ShelfSummary(id: 5, name: "Gespeichertes Regal", slug: "saved")]
        mock.booksToReturn   = [BookSummary(id: 50, name: "Gespeichertes Buch", slug: "saved-b")]

        let defaults = makeDefaults()
        defaults.set(5,  forKey: "shareExtension.lastShelfID")
        defaults.set(50, forKey: "shareExtension.lastBookID")

        let vm = ShareViewModel(
            sharedText: "Test",
            apiService: mock,
            defaults:   defaults
        )

        await vm.loadShelves()

        #expect(vm.selectedShelf?.id == 5,  "Letztes Regal soll wiederhergestellt sein")
        #expect(vm.selectedBook?.id  == 50, "Letztes Buch soll wiederhergestellt sein")
    }

    // MARK: - Additional: title auto-populated from first line

    @Test("Seitentitel wird aus erster Zeile des geteilten Textes befüllt")
    func titleAutoPopulatedFromFirstLine() {
        let text = "Erste Zeile\nZweite Zeile\nDritte Zeile"
        let vm   = ShareViewModel(
            sharedText: text,
            apiService: MockShareAPIService()
        )
        #expect(vm.pageTitle == "Erste Zeile")
    }

    // MARK: - Additional: save page sets isSaved

    @Test("Seite speichern setzt isSaved auf true")
    func savePageSetsisSaved() async throws {
        let mock = MockShareAPIService()
        let vm   = ShareViewModel(
            sharedText: "Inhalt der Seite",
            apiService: mock
        )
        vm.pageTitle     = "Mein Titel"
        vm.selectedBook  = BookSummary(id: 1, name: "Buch", slug: "buch")

        await vm.savePage()

        #expect(vm.isSaved == true)
        #expect(vm.errorMessage == nil)
    }

    // MARK: - Additional: isSaveDisabled logic

    @Test("Speichern ist deaktiviert ohne Titel oder Buch")
    func isSaveDisabledWithoutTitleOrBook() {
        let vm = ShareViewModel(sharedText: "Test", apiService: MockShareAPIService())

        // No title, no book
        vm.pageTitle = ""
        #expect(vm.isSaveDisabled == true)

        // Title but no book
        vm.pageTitle = "Titel"
        #expect(vm.isSaveDisabled == true)

        // Title + book → enabled
        vm.selectedBook = BookSummary(id: 1, name: "Buch", slug: "b")
        #expect(vm.isSaveDisabled == false)
    }
}
