import Foundation
import Testing
@testable import VibeTunnel

// MARK: - Mock Process for Testing

final class MockProcess: Process, @unchecked Sendable {
    var mockIsRunning = false
    var mockProcessIdentifier: Int32 = 12_345
    var mockShouldFailToRun = false
    var runCalled = false
    var terminateCalled = false

    override var isRunning: Bool {
        mockIsRunning
    }

    override var processIdentifier: Int32 {
        mockProcessIdentifier
    }

    override func run() throws {
        runCalled = true
        if mockShouldFailToRun {
            throw CocoaError(.fileNoSuchFile)
        }
        mockIsRunning = true
    }

    override func terminate() {
        terminateCalled = true
        mockIsRunning = false
    }
}

// MARK: - Mock Terminal Manager

actor MockTerminalManager {
    var mockSessions: [UUID: TunnelSession] = [:]
    var mockProcesses: [UUID: MockProcess] = [:]
    var createSessionShouldFail = false
    var executeCommandShouldFail = false
    var executeCommandOutput = ("", "")

    func createSession(request: CreateSessionRequest) throws -> TunnelSession {
        if createSessionShouldFail {
            throw TunnelError.invalidRequest
        }

        let session = TunnelSession()
        mockSessions[session.id] = session

        let process = MockProcess()
        process.mockProcessIdentifier = Int32.random(in: 1_000...9_999)
        mockProcesses[session.id] = process

        return session
    }

    func executeCommand(sessionId: UUID, command: String) async throws -> (output: String, error: String) {
        if executeCommandShouldFail {
            throw TunnelError.commandExecutionFailed("Mock failure")
        }

        guard mockSessions[sessionId] != nil else {
            throw TunnelError.sessionNotFound
        }

        return executeCommandOutput
    }

    func listSessions() -> [TunnelSession] {
        Array(mockSessions.values)
    }

    func getSession(id: UUID) -> TunnelSession? {
        mockSessions[id]
    }

    func closeSession(id: UUID) {
        mockProcesses[id]?.terminate()
        mockProcesses.removeValue(forKey: id)
        mockSessions.removeValue(forKey: id)
    }

    func reset() {
        mockSessions = [:]
        mockProcesses = [:]
        createSessionShouldFail = false
        executeCommandShouldFail = false
        executeCommandOutput = ("", "")
    }

    func setCreateSessionShouldFail(_ value: Bool) {
        createSessionShouldFail = value
    }

    func setExecuteCommandOutput(_ value: (String, String)) {
        executeCommandOutput = value
    }
}

// MARK: - Terminal Manager Tests

@Suite("Terminal Manager Tests")
struct TerminalManagerTests {
    // MARK: - Terminal Detection Tests

    @Test("Detecting installed terminals", arguments: [
        "/bin/bash",
        "/bin/zsh",
        "/bin/sh"
    ])
    func terminalDetection(shell: String) throws {
        // Verify common shells exist on the system
        let shellExists = FileManager.default.fileExists(atPath: shell)

        if shellExists {
            #expect(FileManager.default.isExecutableFile(atPath: shell))
        }
    }

    @Test("Default terminal selection")
    func defaultTerminal() async throws {
        let manager = MockTerminalManager()

        // Create session with default shell
        let request = CreateSessionRequest()
        let session = try await manager.createSession(request: request)

        #expect(session.id != UUID())
        #expect(session.isActive)
        #expect(await manager.mockSessions.count == 1)
    }

    // MARK: - Session Creation Tests

    @Test("Create terminal session with custom shell", arguments: [
        "/bin/bash",
        "/bin/zsh",
        "/usr/bin/env"
    ])
    func createSessionWithShell(shell: String) async throws {
        let manager = MockTerminalManager()

        let request = CreateSessionRequest(shell: shell)
        let session = try await manager.createSession(request: request)

        #expect(session.isActive)
        #expect(session.createdAt <= Date())
        #expect(session.lastActivity >= session.createdAt)
    }

    @Test("Create session with working directory")
    func createSessionWithWorkingDirectory() async throws {
        let manager = MockTerminalManager()

        let tempDir = FileManager.default.temporaryDirectory.path
        let request = CreateSessionRequest(workingDirectory: tempDir)
        let session = try await manager.createSession(request: request)

        #expect(session.isActive)
        #expect(await manager.getSession(id: session.id) != nil)
    }

    @Test("Create session with environment variables")
    func createSessionWithEnvironment() async throws {
        let manager = MockTerminalManager()

        let env = [
            "CUSTOM_VAR": "test_value",
            "PATH": "/custom/path:/usr/bin"
        ]
        let request = CreateSessionRequest(environment: env)
        let session = try await manager.createSession(request: request)

        #expect(session.isActive)
    }

    @Test("Session creation failure")
    func sessionCreationFailure() async throws {
        let manager = MockTerminalManager()
        await manager.reset()
        await manager.setCreateSessionShouldFail(true)

        await #expect(throws: TunnelError.invalidRequest) {
            _ = try await manager.createSession(request: CreateSessionRequest())
        }

        #expect(await manager.mockSessions.isEmpty)
    }

    // MARK: - Command Execution Tests

    @Test("Execute command in session", arguments: [
        "ls -la",
        "pwd",
        "echo 'Hello, World!'",
        "date"
    ])
    func commandExecution(command: String) async throws {
        let manager = MockTerminalManager()

        // Create session
        let session = try await manager.createSession(request: CreateSessionRequest())

        // Set expected output
        await manager.setExecuteCommandOutput(("Command output\n", ""))

        // Execute command
        let (output, error) = try await manager.executeCommand(
            sessionId: session.id,
            command: command
        )

        #expect(output == "Command output\n")
        #expect(error.isEmpty)
    }

    @Test("Execute command with error output")
    func commandWithError() async throws {
        let manager = MockTerminalManager()

        let session = try await manager.createSession(request: CreateSessionRequest())
        await manager.setExecuteCommandOutput(("", "Command not found\n"))

        let (output, error) = try await manager.executeCommand(
            sessionId: session.id,
            command: "nonexistent-command"
        )

        #expect(output.isEmpty)
        #expect(error == "Command not found\n")
    }

    @Test("Execute command in non-existent session")
    func commandInNonExistentSession() async throws {
        let manager = MockTerminalManager()
        let fakeId = UUID()

        await #expect(throws: TunnelError.sessionNotFound) {
            _ = try await manager.executeCommand(
                sessionId: fakeId,
                command: "ls"
            )
        }
    }

    @Test("Command execution timeout")
    func commandTimeout() async throws {
        // Test that timeout is handled properly
        let error = TunnelError.timeout
        #expect(error.errorDescription == "Operation timed out")
    }

    // MARK: - Session Management Tests

    @Test("List all sessions")
    func testListSessions() async throws {
        let manager = MockTerminalManager()

        // Create multiple sessions
        let session1 = try await manager.createSession(request: CreateSessionRequest())
        let session2 = try await manager.createSession(request: CreateSessionRequest())
        let session3 = try await manager.createSession(request: CreateSessionRequest())

        let sessions = await manager.listSessions()

        #expect(sessions.count == 3)
        #expect(sessions.map(\.id).contains(session1.id))
        #expect(sessions.map(\.id).contains(session2.id))
        #expect(sessions.map(\.id).contains(session3.id))
    }

    @Test("Get specific session")
    func testGetSession() async throws {
        let manager = MockTerminalManager()

        let session = try await manager.createSession(request: CreateSessionRequest())

        let retrieved = await manager.getSession(id: session.id)
        #expect(retrieved?.id == session.id)
        #expect(retrieved?.isActive == true)

        // Non-existent session
        let nonExistent = await manager.getSession(id: UUID())
        #expect(nonExistent == nil)
    }

    @Test("Close session")
    func testCloseSession() async throws {
        let manager = MockTerminalManager()

        let session = try await manager.createSession(request: CreateSessionRequest())
        #expect(await manager.mockSessions.count == 1)

        await manager.closeSession(id: session.id)

        #expect(await manager.mockSessions.isEmpty)
        #expect(await manager.getSession(id: session.id) == nil)

        // Verify process was terminated
        let process = await manager.mockProcesses[session.id]
        #expect(process == nil)
    }

    @Test("Close non-existent session")
    func closeNonExistentSession() async throws {
        let manager = MockTerminalManager()
        let fakeId = UUID()

        // Should not throw, just silently do nothing
        await manager.closeSession(id: fakeId)

        #expect(await manager.mockSessions.isEmpty)
    }

    // MARK: - Session Cleanup Tests

    @Test("Cleanup inactive sessions")
    func testCleanupInactiveSessions() async throws {
        let manager = TerminalManager()

        // This test documents expected behavior
        // In real implementation, sessions older than specified minutes would be cleaned up
        await manager.cleanupInactiveSessions(olderThan: 30)

        // After cleanup, only active/recent sessions should remain
        let remainingSessions = await manager.listSessions()
        for session in remainingSessions {
            #expect(session.lastActivity > Date().addingTimeInterval(-30 * 60))
        }
    }

    // MARK: - Concurrent Operations Tests

    @Test("Concurrent session creation", .tags(.concurrency))
    func concurrentSessionCreation() async throws {
        let manager = MockTerminalManager()

        let sessionIds = await withTaskGroup(of: UUID?.self) { group in
            for i in 0..<5 {
                group.addTask {
                    do {
                        let request = CreateSessionRequest(
                            workingDirectory: "/tmp/session-\(i)"
                        )
                        let session = try await manager.createSession(request: request)
                        return session.id
                    } catch {
                        return nil
                    }
                }
            }

            var ids: [UUID] = []
            for await id in group {
                if let id {
                    ids.append(id)
                }
            }
            return ids
        }

        #expect(sessionIds.count == 5)
        #expect(Set(sessionIds).count == 5) // All unique
        #expect(await manager.mockSessions.count == 5)
    }

    @Test("Concurrent command execution", .tags(.concurrency))
    func concurrentCommandExecution() async throws {
        let manager = MockTerminalManager()

        // Create a session
        let session = try await manager.createSession(request: CreateSessionRequest())
        await manager.setExecuteCommandOutput(("OK\n", ""))

        // Execute multiple commands concurrently
        let results = await withTaskGroup(of: Result<String, Error>.self) { group in
            for i in 0..<3 {
                group.addTask {
                    do {
                        let (output, _) = try await manager.executeCommand(
                            sessionId: session.id,
                            command: "echo \(i)"
                        )
                        return .success(output)
                    } catch {
                        return .failure(error)
                    }
                }
            }

            var outputs: [String] = []
            for await result in group {
                if case .success(let output) = result {
                    outputs.append(output)
                }
            }
            return outputs
        }

        #expect(results.count == 3)
        #expect(results.allSatisfy { $0 == "OK\n" })
    }

    // MARK: - Error Handling Tests

    @Test("Terminal error types")
    func errorTypes() throws {
        let errors: [TunnelError] = [
            .sessionNotFound,
            .commandExecutionFailed("Test failure"),
            .timeout,
            .invalidRequest
        ]

        for error in errors {
            #expect(error.errorDescription != nil)
            #expect(!error.errorDescription!.isEmpty)
        }
    }

    // MARK: - Integration Tests

    @Test("Full session lifecycle", .tags(.integration))
    func fullSessionLifecycle() async throws {
        let manager = MockTerminalManager()

        // 1. Create session
        let request = CreateSessionRequest(
            workingDirectory: "/tmp",
            environment: ["TEST": "value"],
            shell: "/bin/bash"
        )
        let session = try await manager.createSession(request: request)

        // 2. Verify session exists
        let retrieved = await manager.getSession(id: session.id)
        #expect(retrieved != nil)
        #expect(retrieved?.isActive == true)

        // 3. Execute commands
        await manager.setExecuteCommandOutput(("test output\n", ""))
        let (output1, _) = try await manager.executeCommand(
            sessionId: session.id,
            command: "echo test"
        )
        #expect(output1 == "test output\n")

        // 4. List sessions
        let sessions = await manager.listSessions()
        #expect(sessions.count == 1)

        // 5. Close session
        await manager.closeSession(id: session.id)

        // 6. Verify cleanup
        #expect(await manager.getSession(id: session.id) == nil)
        #expect(await manager.listSessions().isEmpty)
    }
}
