import SwiftUI
import OSLog

private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "OppiMac", category: "ServerInitView")

/// Step 3: Initialize the server config and start the server.
///
/// Runs `node <cli> init --yes` then starts the server process
/// and waits for /health to respond.
struct ServerInitView: View {

    let processManager: ServerProcessManager
    let healthMonitor: ServerHealthMonitor
    let onContinue: () -> Void
    let onBack: () -> Void

    @State private var phase: InitPhase = .idle
    @State private var error: String?

    var body: some View {
        VStack(spacing: 0) {
            VStack(spacing: 12) {
                Text("Server Setup")
                    .font(.title2)
                    .fontWeight(.semibold)

                Text("Oppi will create its configuration and start the local server.")
                    .multilineTextAlignment(.center)
                    .foregroundStyle(.secondary)
                    .frame(maxWidth: 400)
            }
            .padding(.top, 24)

            Spacer()

            VStack(alignment: .leading, spacing: 12) {
                InitStepRow(
                    label: "Creating config",
                    done: phase.rawValue > InitPhase.creatingConfig.rawValue,
                    active: phase == .creatingConfig
                )
                InitStepRow(
                    label: "Starting server",
                    done: phase.rawValue > InitPhase.startingServer.rawValue,
                    active: phase == .startingServer
                )
                InitStepRow(
                    label: "Waiting for health check",
                    done: phase == .ready,
                    active: phase == .waitingHealth
                )

                if let error {
                    Text(error)
                        .font(.caption)
                        .foregroundStyle(.red)
                        .padding(.top, 4)
                }
            }
            .frame(maxWidth: 320)

            Spacer()

            HStack {
                Button("Back") {
                    onBack()
                }
                .disabled(phase.isRunning)

                Spacer()

                if phase == .idle || error != nil {
                    Button("Initialize & Start") {
                        startInit()
                    }
                    .keyboardShortcut(.defaultAction)
                } else if phase == .ready {
                    Button("Continue") {
                        onContinue()
                    }
                    .keyboardShortcut(.defaultAction)
                }
            }
            .padding(20)
        }
    }

    // MARK: - Init sequence

    private func startInit() {
        error = nil
        phase = .creatingConfig

        Task {
            do {
                // Step 1: Initialize only when this is a new server. Existing
                // bundled-runtime users already have config and only need the npm CLI.
                let configPath = (ServerProcessManager.serverDataDir as NSString)
                    .appendingPathComponent("config.json")
                if !FileManager.default.fileExists(atPath: configPath) {
                    try await runServerInit()
                }

                // Step 2: Start or attach to the local server. A LaunchAgent or
                // prior debug app may already own port 7749, so probe health
                // before spawning another child process.
                await MainActor.run { phase = .startingServer }
                let started = await MacServerLifecycle.startOrAttachFromLocalConfig(
                    processManager: processManager,
                    healthMonitor: healthMonitor,
                    allowKillingExistingServer: true
                )
                guard started else {
                    throw InitError.noToken
                }

                await MainActor.run { phase = .waitingHealth }

                // Step 3: Wait for health monitor to detect healthy state.
                let healthy = try await waitForHealthMonitor()

                await MainActor.run {
                    if healthy {
                        phase = .ready
                    } else {
                        error = "Server did not become healthy after 30 seconds"
                        phase = .idle
                    }
                }
            } catch {
                await MainActor.run {
                    self.error = error.localizedDescription
                    phase = .idle
                }
            }
        }
    }

    private nonisolated func runServerInit() async throws {
        let runtime = await MainActor.run {
            (
                path: ServerProcessManager.resolveRuntimePath(),
                failure: ServerProcessManager.runtimeFailureReason()
            )
        }
        guard let runtimePath = runtime.path else {
            throw InitError.runtimeUnavailable(runtime.failure)
        }
        let nodePath = runtimePath
        guard let cliPath = await MainActor.run(body: { ServerProcessManager.resolveServerCLIPath() }) else {
            throw InitError.cliNotFound
        }

        let result = try await ProcessRunner.runCapturingStderr(
            executable: nodePath,
            arguments: [cliPath, "init", "--yes"]
        )

        if result.exitCode != 0 {
            let errText = result.stderr.isEmpty ? "Unknown error" : result.stderr
            throw InitError.initFailed(errText)
        }

        logger.warning("Server init completed successfully")
    }

    /// Wait for the health monitor to report healthy. The monitor polls every 2s
    /// during startup, so we just watch its state for up to 60 seconds.
    private func waitForHealthMonitor() async throws -> Bool {
        for _ in 0..<30 {
            if healthMonitor.isHealthy {
                return true
            }
            try await Task.sleep(for: .seconds(2))
        }
        return false
    }
}

// MARK: - Types

private enum InitPhase: Int {
    case idle = 0
    case creatingConfig = 1
    case startingServer = 2
    case waitingHealth = 3
    case ready = 4

    var isRunning: Bool {
        self == .creatingConfig || self == .startingServer || self == .waitingHealth
    }
}

private enum InitError: LocalizedError {
    case runtimeUnavailable(String)
    case cliNotFound
    case initFailed(String)
    case noToken

    var errorDescription: String? {
        switch self {
        case .runtimeUnavailable(let message): message
        case .cliNotFound: "Server CLI not found"
        case .initFailed(let msg): "Server init failed: \(msg)"
        case .noToken: "Could not read owner token from config"
        }
    }
}

// MARK: - Step row

private struct InitStepRow: View {
    let label: String
    let done: Bool
    let active: Bool

    var body: some View {
        HStack(spacing: 8) {
            if done {
                Image(systemName: "checkmark.circle.fill")
                    .foregroundStyle(.green)
            } else if active {
                ProgressView()
                    .controlSize(.small)
                    .frame(width: 16, height: 16)
            } else {
                Image(systemName: "circle")
                    .foregroundStyle(.secondary)
            }

            Text(label)
                .foregroundStyle(active || done ? .primary : .secondary)
        }
    }
}
