//
//  ToastManagerTests.swift
//  lichunWebsocketTests
//
//  Unit tests for ToastManager
//

import XCTest
import Combine
@testable import lichunWebsocket

final class ToastManagerTests: XCTestCase {

    var toastManager: ToastManager!
    var cancellables: Set<AnyCancellable>!

    override func setUpWithError() throws {
        try super.setUpWithError()
        continueAfterFailure = false

        // Create a new toast manager instance for testing
        toastManager = ToastManager.shared
        toastManager.clearQueue()
        toastManager.dismissCurrent()

        cancellables = Set<AnyCancellable>()
    }

    override func tearDownWithError() throws {
        // Clean up after tests
        toastManager.dismissCurrent()
        toastManager.clearQueue()
        toastManager = nil
        cancellables = nil
        try super.tearDown()
    }

    // MARK: - Toast Display Tests

    /// Test that toast shows correctly
    func testToastDisplay() throws {
        // Given: Expectation for toast display
        let expectation = XCTestExpectation(description: "Toast should be displayed")

        // When: Showing a toast
        toastManager.$currentToast
            .dropFirst() // Skip initial nil value
            .sink { toast in
                // Then: Toast should be displayed
                if toast != nil {
                    XCTAssertNotNil(toast)
                    XCTAssertEqual(toast?.type, .success)
                    XCTAssertEqual(toast?.message, "Test success message")
                    expectation.fulfill()
                }
            }
            .store(in: &cancellables)

        // Show toast on main thread
        DispatchQueue.main.async {
            self.toastManager.showSuccess("Test success message")
        }

        wait(for: [expectation], timeout: 2.0)
    }

    /// Test different toast types
    func testToastTypes() throws {
        // Test success toast
        let successExpectation = XCTestExpectation(description: "Success toast")
        toastManager.$currentToast
            .dropFirst()
            .sink { toast in
                if toast?.type == .success {
                    XCTAssertEqual(toast?.message, "Success message")
                    successExpectation.fulfill()
                }
            }
            .store(in: &cancellables)

        toastManager.showSuccess("Success message")
        wait(for: [successExpectation], timeout: 1.0)

        // Clear for next test
        toastManager.dismissCurrent()
        Thread.sleep(forTimeInterval: 0.5)

        // Test error toast
        let errorExpectation = XCTestExpectation(description: "Error toast")
        toastManager.$currentToast
            .dropFirst()
            .sink { toast in
                if toast?.type == .error {
                    XCTAssertEqual(toast?.message, "Error message")
                    errorExpectation.fulfill()
                }
            }
            .store(in: &cancellables)

        toastManager.showError("Error message")
        wait(for: [errorExpectation], timeout: 1.0)

        // Clear for next test
        toastManager.dismissCurrent()
        Thread.sleep(forTimeInterval: 0.5)

        // Test info toast
        let infoExpectation = XCTestExpectation(description: "Info toast")
        toastManager.$currentToast
            .dropFirst()
            .sink { toast in
                if toast?.type == .info {
                    XCTAssertEqual(toast?.message, "Info message")
                    infoExpectation.fulfill()
                }
            }
            .store(in: &cancellables)

        toastManager.showInfo("Info message")
        wait(for: [infoExpectation], timeout: 1.0)

        // Clear for next test
        toastManager.dismissCurrent()
        Thread.sleep(forTimeInterval: 0.5)

        // Test warning toast
        let warningExpectation = XCTestExpectation(description: "Warning toast")
        toastManager.$currentToast
            .dropFirst()
            .sink { toast in
                if toast?.type == .warning {
                    XCTAssertEqual(toast?.message, "Warning message")
                    warningExpectation.fulfill()
                }
            }
            .store(in: &cancellables)

        toastManager.showWarning("Warning message")
        wait(for: [warningExpectation], timeout: 1.0)
    }

    // MARK: - Toast Queue Tests

    /// Test that multiple toasts queue properly
    func testToastQueue() throws {
        // Given: Multiple toasts to show
        let toast1Message = "First toast"
        let toast2Message = "Second toast"
        let toast3Message = "Third toast"

        // When: Showing multiple toasts rapidly
        toastManager.showInfo(toast1Message, duration: 0.5)
        toastManager.showSuccess(toast2Message, duration: 0.5)
        toastManager.showWarning(toast3Message, duration: 0.5)

        // Then: First toast should be current
        let expectation = XCTestExpectation(description: "First toast should show")

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
            guard let self = self else { return }
            XCTAssertNotNil(self.toastManager.currentToast)
            XCTAssertEqual(self.toastManager.currentToast?.message, toast1Message)

            // Queue should contain remaining toasts
            XCTAssertGreaterThanOrEqual(self.toastManager.toastQueue.count, 0)
            expectation.fulfill()
        }

        wait(for: [expectation], timeout: 2.0)
    }

    /// Test that toast queue can be cleared
    func testClearQueue() throws {
        // Given: Multiple toasts in queue
        toastManager.showInfo("Toast 1", duration: 10.0) // Long duration to keep it active
        toastManager.showInfo("Toast 2", duration: 10.0)
        toastManager.showInfo("Toast 3", duration: 10.0)

        let expectation = XCTestExpectation(description: "Queue should have items")

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
            guard let self = self else { return }

            // When: Clearing the queue
            self.toastManager.clearQueue()

            // Then: Queue should be empty
            XCTAssertTrue(self.toastManager.toastQueue.isEmpty)
            expectation.fulfill()
        }

        wait(for: [expectation], timeout: 1.0)
    }

    // MARK: - Auto-Dismiss Tests

    /// Test that toast dismisses automatically after duration
    func testAutoDismiss() throws {
        // Given: A toast with short duration
        let shortDuration: TimeInterval = 0.5
        let expectation = XCTestExpectation(description: "Toast should auto-dismiss")
        var toastShown = false
        var toastDismissed = false

        toastManager.$currentToast
            .sink { toast in
                if toast != nil && !toastShown {
                    toastShown = true
                } else if toast == nil && toastShown && !toastDismissed {
                    toastDismissed = true
                    expectation.fulfill()
                }
            }
            .store(in: &cancellables)

        // When: Showing toast with short duration
        toastManager.show(.info, message: "Auto-dismiss test", duration: shortDuration)

        // Then: Toast should dismiss automatically
        wait(for: [expectation], timeout: shortDuration + 1.0)
        XCTAssertTrue(toastDismissed)
    }

    /// Test custom duration
    func testCustomDuration() throws {
        // Given: A toast with custom duration
        let customDuration: TimeInterval = 1.0

        // When: Showing toast with custom duration
        toastManager.show(.success, message: "Custom duration", duration: customDuration)

        // Then: Toast should be visible
        XCTAssertNotNil(toastManager.currentToast)
        XCTAssertEqual(toastManager.currentToast?.duration, customDuration)
    }

    // MARK: - Manual Dismiss Tests

    /// Test that dismissCurrent() works
    func testManualDismiss() throws {
        // Given: A toast is showing
        let expectation = XCTestExpectation(description: "Toast should show then dismiss")
        var didShow = false

        toastManager.$currentToast
            .sink { toast in
                if toast != nil && !didShow {
                    didShow = true
                } else if toast == nil && didShow {
                    // Toast was dismissed
                    expectation.fulfill()
                }
            }
            .store(in: &cancellables)

        // Show toast with long duration so it doesn't auto-dismiss during test
        toastManager.showInfo("Manual dismiss test", duration: 10.0)

        // Wait briefly for toast to appear
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
            // When: Manually dismissing
            self?.toastManager.dismissCurrent()
        }

        // Then: Toast should be dismissed
        wait(for: [expectation], timeout: 2.0)
    }

    /// Test dismissing when no toast is showing
    func testDismissWithNoToast() throws {
        // Given: No toast is showing
        XCTAssertNil(toastManager.currentToast)

        // When: Calling dismiss
        toastManager.dismissCurrent()

        // Then: Should not crash
        XCTAssertNil(toastManager.currentToast)
    }

    // MARK: - Toast Item Tests

    /// Test ToastItem creation and equality
    func testToastItemCreation() throws {
        // Given: Toast item parameters
        let type = ToastType.success
        let message = "Test message"
        let duration: TimeInterval = 3.0

        // When: Creating toast item
        let toastItem = ToastItem(type: type, message: message, duration: duration)

        // Then: Properties should be set correctly
        XCTAssertEqual(toastItem.type, type)
        XCTAssertEqual(toastItem.message, message)
        XCTAssertEqual(toastItem.duration, duration)
        XCTAssertNotNil(toastItem.id)
    }

    /// Test ToastItem equality
    func testToastItemEquality() throws {
        // Given: Two toast items
        let toast1 = ToastItem(type: .success, message: "Test", duration: 3.0)
        let toast2 = ToastItem(type: .success, message: "Test", duration: 3.0)

        // When: Comparing them
        // Then: Should be different (different IDs)
        XCTAssertNotEqual(toast1, toast2)

        // Same instance should equal itself
        XCTAssertEqual(toast1, toast1)
    }

    // MARK: - Toast Type Properties Tests

    /// Test ToastType properties
    func testToastTypeProperties() throws {
        // Test success type
        XCTAssertEqual(ToastType.success.icon, "checkmark.circle.fill")

        // Test error type
        XCTAssertEqual(ToastType.error.icon, "xmark.circle.fill")

        // Test info type
        XCTAssertEqual(ToastType.info.icon, "info.circle.fill")

        // Test warning type
        XCTAssertEqual(ToastType.warning.icon, "exclamationmark.triangle.fill")
    }

    // MARK: - Integration Tests

    /// Test showing multiple toasts sequentially
    func testSequentialToasts() throws {
        // Given: Short duration toasts
        let duration: TimeInterval = 0.3
        let expectation = XCTestExpectation(description: "Sequential toasts should show")
        var toastCount = 0

        toastManager.$currentToast
            .dropFirst()
            .sink { toast in
                if toast != nil {
                    toastCount += 1
                    if toastCount >= 2 {
                        expectation.fulfill()
                    }
                }
            }
            .store(in: &cancellables)

        // When: Showing toasts sequentially
        toastManager.showInfo("First toast", duration: duration)

        DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.4) { [weak self] in
            self?.toastManager.showSuccess("Second toast", duration: duration)
        }

        // Then: Both toasts should show
        wait(for: [expectation], timeout: 3.0)
    }
}
