// ThemeTests.swift
// Unit tests for TerminalTheme, ThemeColor, ThemeStore, and FontStore.
//
// These tests exercise built-in theme validity and store selection logic.
// No UIKit rendering or simulator is required for the pure-struct tests;
// ThemeStore / FontStore tests require @MainActor and a run loop.

import XCTest
@testable import piRemote

final class ThemeTests: XCTestCase {

    // =========================================================================
    // MARK: 1. TerminalTheme — built-in theme invariants
    // =========================================================================

    func testDark_hasExactly16AnsiColors() {
        XCTAssertEqual(TerminalTheme.dark.ansiColors.count, 16,
                       ".dark must have exactly 16 ANSI color entries")
    }

    func testGitHub_hasExactly16AnsiColors() {
        XCTAssertEqual(TerminalTheme.github.ansiColors.count, 16,
                       ".github must have exactly 16 ANSI color entries")
    }

    // =========================================================================
    // MARK: 2. ThemeColor values are in the 0–255 range
    // =========================================================================
    // ThemeColor stores UInt8 components, so they are structurally bounded to
    // 0–255.  These tests make the contract explicit and guard against future
    // refactors that might widen the type.

    func testDark_allAnsiColorComponents_inRange() {
        for (index, color) in TerminalTheme.dark.ansiColors.enumerated() {
            assertColorInRange(color, label: "dark[\(index)]")
        }
        assertColorInRange(TerminalTheme.dark.foreground, label: "dark.foreground")
        assertColorInRange(TerminalTheme.dark.background, label: "dark.background")
        assertColorInRange(TerminalTheme.dark.cursor,     label: "dark.cursor")
    }

    func testGitHub_allAnsiColorComponents_inRange() {
        for (index, color) in TerminalTheme.github.ansiColors.enumerated() {
            assertColorInRange(color, label: "github[\(index)]")
        }
        assertColorInRange(TerminalTheme.github.foreground, label: "github.foreground")
        assertColorInRange(TerminalTheme.github.background, label: "github.background")
        assertColorInRange(TerminalTheme.github.cursor,     label: "github.cursor")
    }

    private func assertColorInRange(_ color: ThemeColor, label: String) {
        // UInt8 is intrinsically 0–255; the assertions below make the expectation readable.
        XCTAssertLessThanOrEqual(color.r, 255, "\(label).r out of range")
        XCTAssertLessThanOrEqual(color.g, 255, "\(label).g out of range")
        XCTAssertLessThanOrEqual(color.b, 255, "\(label).b out of range")
    }

    // =========================================================================
    // MARK: 3. .dark and .github are distinct themes
    // =========================================================================

    func testDarkAndGitHub_haveDistinctIds() {
        XCTAssertNotEqual(TerminalTheme.dark.id, TerminalTheme.github.id,
                          "Built-in themes must have unique IDs")
    }

    func testDarkAndGitHub_haveDistinctBackgrounds() {
        // "Dark (Hacker)" has a pure-black background; GitHub Dark does not.
        XCTAssertNotEqual(TerminalTheme.dark.background, TerminalTheme.github.background,
                          "Background colors must differ between dark and github themes")
    }

    func testDarkAndGitHub_areNotEqual() {
        XCTAssertNotEqual(TerminalTheme.dark, TerminalTheme.github,
                          ".dark and .github must be distinct themes")
    }

    // =========================================================================
    // MARK: 4. TerminalTheme identities
    // =========================================================================

    func testDark_hasExpectedId() {
        XCTAssertEqual(TerminalTheme.dark.id, "dark")
    }

    func testGitHub_hasExpectedId() {
        XCTAssertEqual(TerminalTheme.github.id, "github")
    }

    func testDark_backgroundIsBlack() {
        let bg = TerminalTheme.dark.background
        XCTAssertEqual(bg.r, 0x00, "Dark background R must be 0x00")
        XCTAssertEqual(bg.g, 0x00, "Dark background G must be 0x00")
        XCTAssertEqual(bg.b, 0x00, "Dark background B must be 0x00")
    }

    func testGitHub_backgroundMatchesSpec() {
        // GitHub Dark background: #0d1117
        let bg = TerminalTheme.github.background
        XCTAssertEqual(bg.r, 0x0D, "GitHub background R must be 0x0D")
        XCTAssertEqual(bg.g, 0x11, "GitHub background G must be 0x11")
        XCTAssertEqual(bg.b, 0x17, "GitHub background B must be 0x17")
    }

    // =========================================================================
    // MARK: 5. TerminalTheme.toSwiftTermColor conversion
    // =========================================================================

    func testToSwiftTermColor_white_mapsTo65535() {
        // 255 × 257 = 65535 (= UInt16.max)
        let white  = ThemeColor(r: 255, g: 255, b: 255)
        let stColor = TerminalTheme.dark.toSwiftTermColor(white)
        XCTAssertEqual(stColor.red,   65535)
        XCTAssertEqual(stColor.green, 65535)
        XCTAssertEqual(stColor.blue,  65535)
    }

    func testToSwiftTermColor_black_mapsToZero() {
        let black   = ThemeColor(r: 0, g: 0, b: 0)
        let stColor = TerminalTheme.dark.toSwiftTermColor(black)
        XCTAssertEqual(stColor.red,   0)
        XCTAssertEqual(stColor.green, 0)
        XCTAssertEqual(stColor.blue,  0)
    }

    func testSwiftTermAnsiColors_has16Entries() {
        XCTAssertEqual(TerminalTheme.dark.swiftTermAnsiColors.count,   16)
        XCTAssertEqual(TerminalTheme.github.swiftTermAnsiColors.count, 16)
    }

    // =========================================================================
    // MARK: 6. ThemeStore — selection and persistence
    // =========================================================================

    @MainActor
    func testThemeStore_available_containsBothBuiltins() {
        let store = ThemeStore.shared
        let ids   = store.available.map(\.id)
        XCTAssertTrue(ids.contains("dark"),   "Available themes must include 'dark'")
        XCTAssertTrue(ids.contains("github"), "Available themes must include 'github'")
    }

    @MainActor
    func testThemeStore_select_updatesCurrent() {
        let store  = ThemeStore.shared
        let before = store.current

        // Select whichever theme is NOT currently active.
        let next = store.available.first(where: { $0.id != before.id })!
        store.select(next)
        XCTAssertEqual(store.current.id, next.id,
                       "select(_:) must update current immediately")

        // Restore original selection so we don't bleed state into other tests.
        store.select(before)
    }

    // =========================================================================
    // MARK: 7. TerminalTheme Codable round-trip
    // =========================================================================

    func testTerminalTheme_codable_roundTrips() throws {
        let theme  = TerminalTheme.dark
        let data   = try JSONEncoder().encode(theme)
        let loaded = try JSONDecoder().decode(TerminalTheme.self, from: data)
        XCTAssertEqual(loaded, theme, "TerminalTheme must survive a JSON round-trip")
    }
}
