import CoreGraphics
import Foundation
import IHatePDFsCore
import PDFKit
import XCTest
@testable import IHatePDFs

@MainActor
final class AppStateWorkflowTests: XCTestCase {
    func testOpeningDocumentStartsInFocusedReadingWorkflow() throws {
        let url = try makeTemporaryPDF()
        defer { try? FileManager.default.removeItem(at: url) }

        let appState = AppState()
        appState.updateWindowWidth(1_280)
        appState.showLeftSidebar = true
        appState.leftSidebarMode = .annotations
        appState.showCommentsSidebar = true
        appState.sidebarMode = .highlights
        appState.commentSearchText = "draft"
        appState.commentFilter = .withComments
        appState.selectedKindFilter = .comment
        appState.selectedAuthorFilter = "Someone"
        appState.selectedStatusFilter = ReviewState.reviewed
        appState.collapsedPageIndexes = [0]

        appState.loadDocument(from: url)

        XCTAssertNotNil(appState.document)
        XCTAssertEqual(appState.documentURL, url)
        XCTAssertFalse(appState.showLeftSidebar)
        XCTAssertEqual(appState.leftSidebarMode, .pages)
        XCTAssertFalse(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .annotations)
        XCTAssertEqual(appState.commentSearchText, "")
        XCTAssertEqual(appState.commentFilter, .all)
        XCTAssertNil(appState.selectedKindFilter)
        XCTAssertEqual(appState.selectedAuthorFilter, "All Authors")
        XCTAssertEqual(appState.selectedStatusFilter, ReviewState.allStatuses)
        XCTAssertTrue(appState.collapsedPageIndexes.isEmpty)
        XCTAssertFalse(appState.hasUnsavedChanges)
    }

    func testOpeningDocumentCollapsesSidebars() throws {
        let url = try makeTemporaryPDF()
        defer { try? FileManager.default.removeItem(at: url) }

        let appState = AppState()
        appState.updateWindowWidth(1_280)
        appState.showLeftSidebar = true
        appState.leftSidebarMode = .annotations
        appState.showCommentsSidebar = true
        appState.sidebarMode = .highlights

        appState.loadDocument(from: url)

        XCTAssertFalse(appState.showLeftSidebar)
        XCTAssertFalse(appState.showCommentsSidebar)
    }

    func testDroppingPDFWhileDocumentOpenReplacesThroughAppState() async throws {
        let firstURL = try makeTemporaryPDF()
        let secondURL = try makeTemporaryPDF()
        defer {
            try? FileManager.default.removeItem(at: firstURL)
            try? FileManager.default.removeItem(at: secondURL)
        }

        let appState = AppState()
        appState.loadDocument(from: firstURL)
        XCTAssertEqual(appState.documentURL, firstURL)

        let provider = try XCTUnwrap(NSItemProvider(contentsOf: secondURL))
        XCTAssertTrue(appState.openDroppedDocument(from: [provider]))

        try await waitUntil {
            appState.documentURL == secondURL
        }

        XCTAssertEqual(appState.documentURL, secondURL)
        XCTAssertNotNil(appState.document)
        XCTAssertFalse(appState.hasUnsavedChanges)
    }

    func testClosingDocumentReturnsToEmptyWindowWorkflow() throws {
        let url = try makeTemporaryPDF()
        defer { try? FileManager.default.removeItem(at: url) }

        let appState = AppState()
        appState.loadDocument(from: url)
        appState.showLeftSidebar = true
        appState.leftSidebarMode = .annotations
        appState.showCommentsSidebar = true
        appState.sidebarMode = .highlights
        appState.searchText = "draft"
        appState.showToolbarSearch = true
        appState.collapsedPageIndexes = [0]

        appState.closeDocument()

        XCTAssertNil(appState.document)
        XCTAssertNil(appState.documentURL)
        XCTAssertFalse(appState.showLeftSidebar)
        XCTAssertEqual(appState.leftSidebarMode, .pages)
        XCTAssertFalse(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .annotations)
        XCTAssertEqual(appState.searchText, "")
        XCTAssertFalse(appState.showToolbarSearch)
        XCTAssertTrue(appState.annotations.isEmpty)
        XCTAssertTrue(appState.bookmarks.isEmpty)
        XCTAssertEqual(appState.currentPageIndex, 0)
        XCTAssertEqual(appState.pageText, "1")
        XCTAssertFalse(appState.hasUnsavedWork)
        XCTAssertEqual(appState.statusMessage, "Closed PDF.")
    }

    func testCompactWorkflowShowsOnlyOneSidebarAtATime() {
        let appState = AppState()
        appState.updateWindowWidth(ReaderAdaptiveLayout.minimumWindowWidth)

        appState.toggleRightSidebar(mode: .highlights)
        XCTAssertTrue(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .highlights)
        XCTAssertFalse(appState.showLeftSidebar)

        appState.togglePageSidebar()
        XCTAssertTrue(appState.showLeftSidebar)
        XCTAssertEqual(appState.leftSidebarMode, .pages)
        XCTAssertFalse(appState.showCommentsSidebar)

        appState.toggleRightSidebar(mode: .highlights)
        XCTAssertTrue(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .highlights)
        XCTAssertFalse(appState.showLeftSidebar)
    }

    func testPageSidebarToggleClosesLeftSidebarEvenWhenMarksAreSelected() {
        let appState = AppState()
        appState.updateWindowWidth(1_280)
        appState.showLeftSidebar = true
        appState.leftSidebarMode = .annotations

        appState.togglePageSidebar()

        XCTAssertFalse(appState.showLeftSidebar)
        XCTAssertEqual(appState.leftSidebarMode, .annotations)
    }

    func testRightSidebarToolbarToggleClosesAndReopensCurrentMode() {
        let appState = AppState()
        appState.updateWindowWidth(1_280)

        appState.toggleRightSidebar(mode: .highlights)
        XCTAssertTrue(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .highlights)

        appState.toggleRightSidebarVisibility()
        XCTAssertFalse(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .highlights)

        appState.toggleRightSidebarVisibility()
        XCTAssertTrue(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .highlights)
    }

    func testRightSidebarToggleClosesFromDifferentOpenMode() {
        let appState = AppState()
        appState.updateWindowWidth(1_280)

        appState.toggleRightSidebar(mode: .highlights)
        XCTAssertTrue(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .highlights)

        appState.toggleRightSidebar(mode: .annotations)
        XCTAssertFalse(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .highlights)

        appState.toggleRightSidebar(mode: .annotations)
        XCTAssertTrue(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .annotations)
    }

    func testRightSidebarToggleFromHighlightsDoesNotSwitchModeWhenClosing() {
        let appState = AppState()
        appState.updateWindowWidth(1_280)

        appState.toggleRightSidebar(mode: .highlights)
        XCTAssertTrue(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .highlights)

        appState.toggleRightSidebarVisibility()
        XCTAssertFalse(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .highlights)

        appState.toggleRightSidebarVisibility()
        XCTAssertTrue(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .highlights)
    }

    func testRightSidebarToolbarToggleDefaultsToCommentsWhenNoModeWasChosen() {
        let appState = AppState()
        appState.updateWindowWidth(1_280)

        XCTAssertFalse(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .annotations)

        appState.toggleRightSidebarVisibility()

        XCTAssertTrue(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .annotations)
    }

    func testRegularWorkflowAllowsNavigationAndReviewSidebarsTogether() {
        let appState = AppState()
        appState.updateWindowWidth(1_000)

        appState.togglePageSidebar()
        appState.toggleRightSidebar(mode: .annotations)

        XCTAssertTrue(appState.showLeftSidebar)
        XCTAssertEqual(appState.leftSidebarMode, .pages)
        XCTAssertTrue(appState.showCommentsSidebar)
        XCTAssertEqual(appState.sidebarMode, .annotations)
    }

    func testSaveAvailabilityTracksReplyDraftAsUnsavedWork() throws {
        let url = try makeTemporaryPDF()
        defer { try? FileManager.default.removeItem(at: url) }

        let appState = AppState()
        appState.loadDocument(from: url)

        XCTAssertFalse(appState.hasUnsavedWork)
        XCTAssertFalse(appState.canSaveDocument)
        XCTAssertEqual(appState.saveHelpText, "No unsaved changes.")

        appState.sidebarReplyDraft = "Need to verify this quote."

        XCTAssertTrue(appState.hasUnsavedWork)
        XCTAssertTrue(appState.hasUnsentSidebarReplyDraft)
        XCTAssertTrue(appState.canSaveDocument)
        XCTAssertEqual(appState.saveHelpText, "Send or cancel the reply draft before saving.")

        appState.hasUnsavedChanges = true
        XCTAssertTrue(appState.canSaveDocument)
        XCTAssertEqual(appState.saveHelpText, "Save PDF")
    }

    func testCancelActiveModeClearsHighlighterAndPlacement() {
        let appState = AppState()
        appState.isHighlighterModeActive = true
        appState.placementTool = .freeText

        XCTAssertTrue(appState.canCancelActiveMode)
        XCTAssertTrue(appState.cancelActiveMode())

        XCTAssertFalse(appState.isHighlighterModeActive)
        XCTAssertNil(appState.placementTool)
        XCTAssertEqual(appState.statusMessage, "Free text placement canceled. Highlighter off.")
        XCTAssertFalse(appState.canCancelActiveMode)
    }

    func testCancelActiveModeReturnsFalseWhenNoModeIsActive() {
        let appState = AppState()

        XCTAssertFalse(appState.canCancelActiveMode)
        XCTAssertFalse(appState.cancelActiveMode())
        XCTAssertEqual(appState.statusMessage, "Open a PDF to begin.")
    }

    func testDeleteSelectedHighlightCanUndoAndRedo() throws {
        let url = try makeTemporaryPDF()
        defer { try? FileManager.default.removeItem(at: url) }

        let window = NSWindow()
        let appState = AppState()
        appState.hostingWindow = window
        appState.loadDocument(from: url)

        let page = try XCTUnwrap(appState.document?.page(at: 0))
        let highlight = PDFAnnotation(
            bounds: CGRect(x: 72, y: 650, width: 180, height: 18),
            forType: .highlight,
            withProperties: nil
        )
        highlight.markupType = .highlight
        page.addAnnotation(highlight)
        appState.refreshAnnotations(on: [page])

        let item = try XCTUnwrap(appState.annotations.first { $0.annotation === highlight })
        appState.selectHighlightedText(item)
        XCTAssertTrue(appState.canDeleteSelectedAnnotation)

        appState.deleteSelectedAnnotation()

        XCTAssertFalse(page.annotations.contains { $0 === highlight })
        XCTAssertTrue(appState.annotations.isEmpty)
        XCTAssertTrue(appState.canUndoAnnotationChange)

        appState.undoAnnotationChange()

        XCTAssertTrue(page.annotations.contains { $0 === highlight })
        XCTAssertEqual(appState.annotations.count, 1)
        XCTAssertTrue(appState.canRedoAnnotationChange)

        appState.redoAnnotationChange()

        XCTAssertFalse(page.annotations.contains { $0 === highlight })
        XCTAssertTrue(appState.annotations.isEmpty)
    }

    private func makeTemporaryPDF() throws -> URL {
        let url = FileManager.default.temporaryDirectory
            .appendingPathComponent(UUID().uuidString)
            .appendingPathExtension("pdf")

        let data = NSMutableData()
        guard let consumer = CGDataConsumer(data: data) else {
            throw TestPDFError.couldNotCreateConsumer
        }

        var mediaBox = CGRect(x: 0, y: 0, width: 612, height: 792)
        guard let context = CGContext(consumer: consumer, mediaBox: &mediaBox, nil) else {
            throw TestPDFError.couldNotCreateContext
        }

        context.beginPDFPage(nil)
        context.endPDFPage()
        context.closePDF()

        try data.write(to: url, options: .atomic)
        return url
    }

    private enum TestPDFError: Error {
        case couldNotCreateConsumer
        case couldNotCreateContext
    }

    private func waitUntil(
        timeout: TimeInterval = 2,
        condition: @MainActor @escaping () -> Bool
    ) async throws {
        let deadline = Date().addingTimeInterval(timeout)
        while Date() < deadline {
            if condition() {
                return
            }
            try await Task.sleep(nanoseconds: 20_000_000)
        }
        XCTFail("Timed out waiting for condition.")
    }
}
