import APIClient
import Foundation
import TestSupport
import Testing
import UIKit
import WireProtocol
@testable import WebTerm

/// T-iPad-3 · 终端指针上下文菜单（右键/长按）。菜单动作与既有通道**一一映射**，
/// 绝不新增网络路径：
/// - `.newInCwd`      → T-iOS-29 的 `onNewInCwd` 闭包（cwd fresh spawn）；
/// - `.kill`          → `onKill`，wiring 侧走 `APIClient.killSession`（带 Origin
///                      的 G 端点，RO/G 铁律不变）；
/// - `.copySelection` → SwiftTerm 自带 copy（读选区 → 剪贴板，绝不改字节流）。
@MainActor
@Suite("TerminalContextMenu (T-iPad-3)")
struct TerminalContextMenuTests {
    private nonisolated static let base = "http://192.168.1.5:3000"

    /// 记录 perform 触发了哪个既有通道（映射断言用）。
    @MainActor
    private final class ChannelRecorder {
        var newInCwd = 0
        var kill = 0
        var copy = 0
    }

    /// 让 onKill 里 fire-and-forget 的 Task 可被确定性 await（零真实等待）。
    @MainActor
    private final class TaskBox {
        var task: Task<Void, Never>?
    }

    private func endpoint() throws -> HostEndpoint {
        let url = try #require(URL(string: Self.base))
        return try #require(HostEndpoint(baseURL: url))
    }

    private func deleteURL(id: UUID) throws -> URL {
        try #require(URL(string: "\(Self.base)/live-sessions/\(id.uuidString.lowercased())"))
    }

    // MARK: - 1. 指针菜单仅 iPad 启用（iPhone 长按保持 SwiftTerm 选区手势，零回归）

    @Test("isPointerMenuEnabled：iPad 开、iPhone 关（idiom 与 size class 正交）")
    func pointerMenuEnabledOnlyOnPad() {
        #expect(TerminalContextMenu.isPointerMenuEnabled(idiom: .pad))
        #expect(!TerminalContextMenu.isPointerMenuEnabled(idiom: .phone))
    }

    // MARK: - 2. 菜单项随可用性过滤（纯函数）

    @Test("三项全可用 → 复制选区、开新会话、结束会话（固定顺序，kill 破坏性）")
    func itemsAllAvailableInOrder() {
        let items = TerminalContextMenu.items(canCopySelection: true, canNewInCwd: true, canKill: true)
        #expect(items.map(\.action) == [.copySelection, .newInCwd, .kill])
        #expect(items.map(\.title) == [
            TerminalContextMenu.Copy.copySelection,
            TerminalContextMenu.Copy.newInCwd,
            TerminalContextMenu.Copy.kill,
        ])
        #expect(items.first { $0.action == .kill }?.isDestructive == true)
    }

    @Test("无选区 → 隐藏复制选区；无 kill 通道 → 隐藏结束会话")
    func itemsFilteredByAvailability() {
        let noCopy = TerminalContextMenu.items(canCopySelection: false, canNewInCwd: true, canKill: true)
        #expect(!noCopy.contains { $0.action == .copySelection })

        let noKill = TerminalContextMenu.items(canCopySelection: true, canNewInCwd: true, canKill: false)
        #expect(!noKill.contains { $0.action == .kill })

        let empty = TerminalContextMenu.items(canCopySelection: false, canNewInCwd: false, canKill: false)
        #expect(empty.isEmpty)
    }

    // MARK: - 3. Model.items 反映注入的可用性 + perform 路由既有通道

    @Test("model.items：onKill=nil → 无结束会话；hasSelection=false → 无复制选区")
    func modelItemsReflectInjectedAvailability() {
        let model = TerminalContextMenuModel(
            onNewInCwd: {},
            onKill: nil,
            onCopySelection: {},
            hasSelection: { false }
        )
        #expect(model.items.map(\.action) == [.newInCwd])
    }

    @Test("perform 把每个动作路由到对应既有闭包（不误触其它通道）")
    func performRoutesToExistingChannels() {
        let recorder = ChannelRecorder()
        let model = TerminalContextMenuModel(
            onNewInCwd: { recorder.newInCwd += 1 },
            onKill: { recorder.kill += 1 },
            onCopySelection: { recorder.copy += 1 },
            hasSelection: { true }
        )

        model.perform(.copySelection)
        #expect((recorder.copy, recorder.newInCwd, recorder.kill) == (1, 0, 0))

        model.perform(.newInCwd)
        #expect((recorder.copy, recorder.newInCwd, recorder.kill) == (1, 1, 0))

        model.perform(.kill)
        #expect((recorder.copy, recorder.newInCwd, recorder.kill) == (1, 1, 1))
    }

    // MARK: - 4. kill 动作复用 APIClient.killSession —— 带 Origin、无旁路网络

    @Test("kill 动作 → 唯一一条 DELETE /live-sessions/:id，带精确 Origin（复用 APIClient，无绕过）")
    func killActionReusesAPIClientWithOrigin() async throws {
        // Arrange：真 APIClient over FakeHTTPTransport —— wiring 供给的 kill 闭包形状。
        let endpoint = try endpoint()
        let http = FakeHTTPTransport()
        let sessionId = UUID()
        await http.queueSuccess(method: "DELETE", url: try deleteURL(id: sessionId), status: 204)
        let api = APIClient(endpoint: endpoint, http: http)
        let box = TaskBox()
        let model = TerminalContextMenuModel(
            onNewInCwd: nil,
            onKill: { box.task = Task { try? await api.killSession(id: sessionId) } },
            onCopySelection: {},
            hasSelection: { false }
        )

        // Act：菜单触发 kill。
        model.perform(.kill)
        await box.task?.value

        // Assert：恰好一条请求，DELETE 到 kill 路由，带精确 Origin（G 铁律）。
        let requests = await http.recordedRequests
        #expect(requests.count == 1)
        let deleteRequest = requests.first { $0.httpMethod == "DELETE" }
        #expect(deleteRequest?.url == (try deleteURL(id: sessionId)))
        #expect(deleteRequest?.value(forHTTPHeaderField: "Origin") == endpoint.originHeader)
    }
}
