package com.hikoncont.service.modules

import android.content.Context
import android.util.Log
import com.hikoncont.network.SocketIOManager
import com.hikoncont.service.AccessibilityRemoteService
import kotlinx.coroutines.*
import org.json.JSONObject

/**
 * Network manager - manages Socket.IO connection lifecycle.
 *
 * Socket.IO client has its own internal reconnect mechanism.
 * NetworkManager cooperates with it rather than fighting it:
 * 1. Phase 1 (fast poll): Quick 10s check for immediate connections
 * 2. Phase 2 (background monitor): If Phase 1 times out, monitor
 *    in background while Socket.IO keeps retrying internally
 * 3. Only trigger full reconnect if background monitor times out (2min)
 */
class NetworkManager(
    private val service: AccessibilityRemoteService,
    private val context: Context
) {
    companion object {
        private const val TAG = "NetworkManager"
        private const val INITIAL_CHECK_INTERVAL_MS = 500L
        private const val INITIAL_CHECK_MAX_ATTEMPTS = 20  // 10s
        private const val BG_MONITOR_INTERVAL_MS = 3000L
        private const val BG_MONITOR_MAX_DURATION_MS = 120_000L  // 2min
        private const val RECONNECT_INTERVAL_MS = 5000L
        private const val MAX_RECONNECT_ATTEMPTS = 10
        private const val RECONNECT_COOLDOWN_MS = 30_000L
        private const val CONFIG_FILE_SERVER = "server_config.json"
    }

    private lateinit var socketIOManager: SocketIOManager
    private val networkScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    @Volatile private var isConnected = false
    @Volatile private var isConnecting = false
    @Volatile private var lastConnectionAttempt = 0L
    private var backgroundMonitorJob: Job? = null
    private var reconnectAttempts = 0
    private var lastReconnectTime = 0L

    /**
     * Initialize NetworkManager and create SocketIOManager instance.
     */
    fun initialize() {
        try {
            Log.i(TAG, "Initializing NetworkManager (Socket.IO)")
            socketIOManager = SocketIOManager(service)
            Log.i(TAG, "NetworkManager initialized")
        } catch (e: Exception) {
            Log.e(TAG, "NetworkManager init failed", e)
        }
    }

    /**
     * Connect to server with two-phase approach:
     * Phase 1: Fast poll for 10s
     * Phase 2: Background monitor for up to 2min
     */
    suspend fun connectToServer() {
        if (isConnecting) {
            Log.d(TAG, "Connection in progress, skip duplicate")
            return
        }
        if (isConnected()) {
            Log.d(TAG, "Already connected, skip")
            return
        }
        try {
            isConnecting = true
            lastConnectionAttempt = System.currentTimeMillis()
            val serverUrl = getServerUrl()
            if (serverUrl.isNullOrBlank()) {
                Log.e(TAG, "Server URL is empty, cannot connect")
                return
            }
            isConnected = false
            Log.i(TAG, "Connecting to server: $serverUrl")

            // Brief delay for SocketIOManager setup
            delay(1000)

            try {
                socketIOManager.connect(serverUrl)
            } catch (e: Exception) {
                Log.e(TAG, "Socket.IO connect call failed: ${e.message}")
            }

            // Phase 1: Fast poll (10s)
            if (pollForConnection(INITIAL_CHECK_MAX_ATTEMPTS, INITIAL_CHECK_INTERVAL_MS)) {
                isConnected = true
                reconnectAttempts = 0
                Log.i(TAG, "Socket.IO connected (Phase 1, fast poll)")
                return
            }

            // Phase 2: Background monitor
            Log.i(TAG, "Phase 1 (10s) did not connect, starting background monitor")
            startBackgroundMonitor()
        } catch (e: Exception) {
            Log.e(TAG, "Connect to server failed: ${e.message}", e)
            isConnected = false
        } finally {
            isConnecting = false
        }
    }

    /**
     * Poll Socket.IO connection status at fixed intervals.
     * @return true if connected within the polling window
     */
    private suspend fun pollForConnection(maxAttempts: Int, intervalMs: Long): Boolean {
        repeat(maxAttempts) { attempt ->
            delay(intervalMs)
            if (isSocketIOReady()) {
                val elapsedMs = (attempt + 1) * intervalMs
                Log.i(TAG, "Socket.IO connected after ${elapsedMs}ms")
                return true
            }
        }
        return false
    }

    /**
     * Background monitor: watches Socket.IO internal reconnect for up to 2min.
     * If connected during this window, updates state.
     * If timeout, triggers force reconnect.
     */
    private fun startBackgroundMonitor() {
        backgroundMonitorJob?.cancel()
        backgroundMonitorJob = networkScope.launch {
            val startTime = System.currentTimeMillis()
            var checkCount = 0
            while (isActive) {
                delay(BG_MONITOR_INTERVAL_MS)
                checkCount++
                try {
                    if (isSocketIOReady()) {
                        isConnected = true
                        reconnectAttempts = 0
                        val sec = (System.currentTimeMillis() - startTime) / 1000
                        Log.i(TAG, "Background monitor: connected after ${sec}s")
                        return@launch
                    }
                    val elapsed = System.currentTimeMillis() - startTime
                    if (elapsed > BG_MONITOR_MAX_DURATION_MS) {
                        Log.w(TAG, "Background monitor timeout (${elapsed / 1000}s), force reconnect")
                        triggerForceReconnect()
                        return@launch
                    }
                    if (checkCount % 10 == 0) {
                        Log.d(TAG, "Background monitor: waiting (${elapsed / 1000}s)")
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "Background monitor check failed: ${e.message}")
                }
            }
        }
    }

    /**
     * Reconnect to server with cooldown and attempt limits.
     */
    fun reconnectToServer() {
        val now = System.currentTimeMillis()
        if (now - lastReconnectTime < RECONNECT_COOLDOWN_MS) {
            Log.d(TAG, "Reconnect cooldown active, skip")
            return
        }
        if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
            Log.w(TAG, "Max reconnect attempts ($MAX_RECONNECT_ATTEMPTS) reached, reset counter")
            reconnectAttempts = 0
        }
        lastReconnectTime = now
        reconnectAttempts++
        Log.i(TAG, "Reconnecting to server (attempt $reconnectAttempts/$MAX_RECONNECT_ATTEMPTS)")

        networkScope.launch {
            try {
                backgroundMonitorJob?.cancel()
                isConnected = false
                isConnecting = false
                connectToServer()
            } catch (e: Exception) {
                Log.e(TAG, "Reconnect failed: ${e.message}", e)
            }
        }
    }

    /**
     * Schedule next reconnect with exponential backoff.
     */
    private fun scheduleNextReconnect() {
        val delayMs = RECONNECT_INTERVAL_MS * (1L shl minOf(reconnectAttempts, 5))
        Log.i(TAG, "Scheduling reconnect in ${delayMs}ms (attempt $reconnectAttempts)")
        networkScope.launch {
            delay(delayMs)
            if (!isConnected()) {
                reconnectToServer()
            }
        }
    }

    /**
     * Check connection and start if not connected.
     * Called externally to verify and recover connection.
     */
    fun checkAndStartConnection() {
        networkScope.launch {
            try {
                if (isConnected()) {
                    Log.d(TAG, "Connection check: already connected")
                    return@launch
                }
                Log.i(TAG, "Connection check: not connected, starting connection")
                connectToServer()
            } catch (e: Exception) {
                Log.e(TAG, "checkAndStartConnection failed: ${e.message}", e)
            }
        }
    }

    /**
     * Start connection (alias for checkAndStartConnection).
     * Used by AccessibilityRemoteService.
     */
    fun startConnection() {
        checkAndStartConnection()
    }

    /**
     * Disconnect from server and cancel background jobs.
     */
    fun disconnect() {
        try {
            Log.i(TAG, "Disconnecting from server")
            backgroundMonitorJob?.cancel()
            backgroundMonitorJob = null
            isConnected = false
            isConnecting = false
            if (::socketIOManager.isInitialized) {
                socketIOManager.disconnect()
            }
            Log.i(TAG, "Disconnected from server")
        } catch (e: Exception) {
            Log.e(TAG, "Disconnect failed: ${e.message}", e)
        }
    }

    /**
     * Cleanup all resources. Call on service destroy.
     */
    fun cleanup() {
        try {
            Log.i(TAG, "Cleaning up NetworkManager resources")
            disconnect()
            networkScope.cancel()
            Log.i(TAG, "NetworkManager cleanup complete")
        } catch (e: Exception) {
            Log.e(TAG, "Cleanup failed: ${e.message}", e)
        }
    }

    /**
     * Get SocketIOManager instance.
     * @return SocketIOManager or null if not initialized
     */
    fun getSocketIOManager(): SocketIOManager? {
        return if (::socketIOManager.isInitialized) {
            socketIOManager
        } else {
            Log.w(TAG, "SocketIOManager not initialized")
            null
        }
    }

    /**
     * Check if currently connected to server.
     */
    fun isConnected(): Boolean {
        // Sync local state with actual Socket.IO state
        if (::socketIOManager.isInitialized) {
            val actualConnected = socketIOManager.isConnected()
            if (isConnected != actualConnected) {
                isConnected = actualConnected
            }
            return actualConnected
        }
        return false
    }

    /**
     * Check if connection is in progress.
     */
    fun isConnecting(): Boolean = isConnecting

    /**
     * Get current connection status summary.
     */
    fun getConnectionStatus(): ConnectionStatus {
        return ConnectionStatus(
            connected = isConnected(),
            connecting = isConnecting,
            reconnectAttempts = reconnectAttempts,
            lastAttemptTime = lastConnectionAttempt
        )
    }

    // -- Config reading methods --

    /**
     * Read server URL from assets config file.
     * @return server URL string or null on failure
     */
    fun getServerUrl(): String? {
        return readServerConfigFromAssets()
    }

    /**
     * Read web URL from assets config file.
     * @return web URL string or null on failure
     */
    fun getWebUrl(): String? {
        return readWebConfigFromAssets()
    }

    /**
     * Parse server_config.json and extract serverUrl.
     */
    private fun readServerConfigFromAssets(): String? {
        return try {
            val jsonStr = context.assets.open(CONFIG_FILE_SERVER)
                .bufferedReader().use { it.readText() }
            val json = JSONObject(jsonStr)
            val url = json.optString("serverUrl", "")
            if (url.isBlank()) {
                Log.e(TAG, "serverUrl is empty in $CONFIG_FILE_SERVER")
                null
            } else {
                url
            }
        } catch (e: Exception) {
            Log.e(TAG, "Failed to read $CONFIG_FILE_SERVER: ${e.message}")
            null
        }
    }

    /**
     * Parse server_config.json and extract webUrl.
     */
    private fun readWebConfigFromAssets(): String? {
        return try {
            val jsonStr = context.assets.open(CONFIG_FILE_SERVER)
                .bufferedReader().use { it.readText() }
            val json = JSONObject(jsonStr)
            val url = json.optString("webUrl", "")
            if (url.isBlank()) {
                Log.e(TAG, "webUrl is empty in $CONFIG_FILE_SERVER")
                null
            } else {
                url
            }
        } catch (e: Exception) {
            Log.e(TAG, "Failed to read webUrl from $CONFIG_FILE_SERVER: ${e.message}")
            null
        }
    }

    // -- State reset methods --

    /**
     * Reset reconnect counter. Called after successful connection.
     */
    fun resetReconnectCounter() {
        reconnectAttempts = 0
        lastReconnectTime = 0L
        Log.d(TAG, "Reconnect counter reset")
    }

    /**
     * Reset all network state for fresh start (e.g. service restart).
     */
    fun resetNetworkState() {
        Log.i(TAG, "Resetting network state")
        backgroundMonitorJob?.cancel()
        backgroundMonitorJob = null
        isConnected = false
        isConnecting = false
        reconnectAttempts = 0
        lastReconnectTime = 0L
        lastConnectionAttempt = 0L
    }

    /**
     * Force reconnect by disconnecting and reconnecting.
     */
    fun forceReconnect() {
        Log.i(TAG, "Force reconnect requested")
        networkScope.launch {
            try {
                disconnect()
                delay(1000)
                connectToServer()
            } catch (e: Exception) {
                Log.e(TAG, "Force reconnect failed: ${e.message}", e)
            }
        }
    }

    // -- Private helpers --

    /**
     * Check if SocketIOManager is initialized and connected.
     */
    private fun isSocketIOReady(): Boolean {
        return ::socketIOManager.isInitialized && socketIOManager.isConnected()
    }

    /**
     * Trigger force reconnect via SocketIOManager.
     */
    private fun triggerForceReconnect() {
        try {
            if (::socketIOManager.isInitialized) {
                socketIOManager.forceReconnect()
            }
        } catch (e: Exception) {
            Log.e(TAG, "triggerForceReconnect failed: ${e.message}")
        }
    }

    /**
     * Connection status data class for external queries.
     */
    data class ConnectionStatus(
        val connected: Boolean,
        val connecting: Boolean,
        val reconnectAttempts: Int,
        val lastAttemptTime: Long
    )
}
