package xyz.kusoft.tradebotmonitor

import android.util.Base64
import org.json.JSONArray
import org.json.JSONObject
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets

class TradeBotApi(
    private val baseUrl: String,
    private val token: String,
) {
    fun fetchSnapshot(): BotSnapshot {
        val health = getJson("/api/health")
        val status = getJson("/api/status")
        val markets = getJson("/api/markets")
        val signals = getJson("/api/signals?limit=220")
        val config = getJson("/api/config")
        val trades = getJson("/api/trades?limit=10")
        val retrain = getJson("/api/retrain")
        val backtest = getJson("/api/backtest")

        val accountJson = status.optJSONObject("account") ?: JSONObject()
        val account = AccountData(
            equity = accountJson.optDouble("equity", 0.0),
            cash = accountJson.optDouble("cash", 0.0),
            exposure = accountJson.optDouble("exposure", 0.0),
        )

        return BotSnapshot(
            ok = health.optBoolean("ok", false),
            running = health.optBoolean("running", false),
            mode = health.optStringClean("mode"),
            account = account,
            positions = parsePositions(status.optJSONArray("positions") ?: JSONArray()),
            closedTrades = parseClosedTrades(trades.optJSONArray("closed_items") ?: JSONArray()),
            closedTradesSummary = parseClosedTradesSummary(trades.optJSONObject("closed_summary") ?: JSONObject()),
            markets = parseMarkets(markets.optJSONArray("markets") ?: JSONArray()),
            signalsBySymbol = parseLatestSignals(signals.optJSONArray("items") ?: JSONArray()),
            config = config,
            retrain = retrain,
            backtest = backtest,
            fetchedAtMillis = System.currentTimeMillis(),
        )
    }

    fun startBot() {
        postJson("/api/control/start")
    }

    fun stopBot() {
        postJson("/api/control/stop")
    }

    fun requestRetrain(): String {
        val response = postJson("/api/training/retrain", "{\"source\":\"android\"}")
        if (response.optBoolean("queued", false)) {
            return "Задание переобучения отправлено на закрепленный компьютер"
        }
        return when (response.optStringClean("reason")) {
            "active_job_exists" -> "Задание переобучения уже выполняется или ждет агента"
            else -> "Задание переобучения принято ботом"
        }
    }

    private fun getJson(path: String): JSONObject = request(path, "GET")

    private fun postJson(path: String, body: String = "{}"): JSONObject = request(path, "POST", body)

    private fun request(path: String, method: String, body: String = "{}"): JSONObject {
        val root = baseUrl.trim().trimEnd('/')
        val url = URL(root + path)
        val connection = (url.openConnection() as HttpURLConnection).apply {
            requestMethod = method
            connectTimeout = 6000
            readTimeout = 9000
            setRequestProperty("Accept", "application/json")
            applyAuthHeaders(this, token)
            if (method == "POST") {
                doOutput = true
                setRequestProperty("Content-Type", "application/json")
                outputStream.use { stream -> stream.write(body.toByteArray(StandardCharsets.UTF_8)) }
            }
        }
        val code = connection.responseCode
        val stream = if (code in 200..299) connection.inputStream else connection.errorStream
        val text = stream?.bufferedReader(StandardCharsets.UTF_8)?.use(BufferedReader::readText).orEmpty()
        connection.disconnect()
        if (code !in 200..299) {
            if (code == HttpURLConnection.HTTP_UNAUTHORIZED) {
                throw IllegalStateException("HTTP 401: сервер требует логин и пароль")
            }
            throw IllegalStateException("HTTP $code: ${text.take(240)}")
        }
        return if (text.isBlank()) JSONObject() else JSONObject(text)
    }

    private fun parsePositions(items: JSONArray): List<PositionData> {
        val output = mutableListOf<PositionData>()
        for (index in 0 until items.length()) {
            val row = items.optJSONObject(index) ?: continue
            val exitPlan = row.optJSONObject("exit_plan") ?: JSONObject()
            output += PositionData(
                id = row.optLongOrNull("id"),
                symbol = row.optStringClean("symbol"),
                qty = row.optDouble("qty", 0.0),
                entryPrice = row.optDouble("entry_price", 0.0),
                markPrice = row.optDouble("mark_price", 0.0),
                notionalUsdt = row.optDouble("notional_usdt", 0.0),
                marketValue = row.optDouble("market_value", 0.0),
                unrealizedPnl = row.optDouble("unrealized_pnl", 0.0),
                unrealizedPnlPercent = row.optDouble("unrealized_pnl_percent", 0.0),
                stopLoss = exitPlan.optDoubleOrNull("stop_loss") ?: row.optDoubleOrNull("stop_loss"),
                takeProfit = exitPlan.optDoubleOrNull("take_profit") ?: row.optDoubleOrNull("take_profit"),
                highestPrice = exitPlan.optDoubleOrNull("highest_price") ?: row.optDoubleOrNull("highest_price"),
                trailingStop = exitPlan.optDoubleOrNull("trailing_stop"),
                atrTrailingStop = exitPlan.optDoubleOrNull("atr_trailing_stop"),
                openedAt = row.optStringClean("opened_at"),
                exitAction = exitPlan.optStringClean("action"),
                exitReason = exitPlan.optStringClean("reason"),
                stopLossExitEnabled = exitPlan.optBooleanOrNull("stop_loss_exit_enabled") ?: true,
            )
        }
        return output
    }

    private fun parseClosedTrades(items: JSONArray): List<ClosedTradeData> {
        val output = mutableListOf<ClosedTradeData>()
        for (index in 0 until items.length()) {
            val row = items.optJSONObject(index) ?: continue
            output += ClosedTradeData(
                symbol = row.optStringClean("symbol"),
                qty = row.optDouble("qty", 0.0),
                entryPrice = row.optDoubleOrNull("entry_price"),
                exitPrice = row.optDoubleOrNull("exit_price"),
                grossPnl = row.optDouble("gross_pnl", 0.0),
                feeUsdt = row.optDouble("fee_usdt", 0.0),
                netPnl = row.optDouble("net_pnl", 0.0),
                reason = row.optStringClean("reason"),
                openedAt = row.optStringClean("opened_at"),
                closedAt = row.optStringClean("closed_at"),
            )
        }
        return output
    }

    private fun parseClosedTradesSummary(row: JSONObject): ClosedTradesSummary =
        ClosedTradesSummary(
            trades = row.optInt("trades", 0),
            netPnl = row.optDouble("net_pnl", 0.0),
            grossPnl = row.optDouble("gross_pnl", 0.0),
            feeUsdt = row.optDouble("fee_usdt", 0.0),
            wins = row.optInt("wins", 0),
            losses = row.optInt("losses", 0),
            winRate = row.optDouble("win_rate", 0.0),
        )

    private fun parseMarkets(items: JSONArray): List<MarketItem> {
        val output = mutableListOf<MarketItem>()
        for (index in 0 until items.length()) {
            val row = items.optJSONObject(index) ?: continue
            val tickerJson = row.optJSONObject("ticker")
            val instrument = row.optJSONObject("instrument") ?: JSONObject()
            val symbol = tickerJson?.optStringClean("symbol").orEmpty().ifBlank {
                instrument.optStringClean("symbol")
            }
            val quality = row.optJSONObject("quality") ?: JSONObject()
            output += MarketItem(
                symbol = symbol,
                ticker = tickerJson?.let(::parseTicker),
                candles = parseCandles(row.optJSONArray("candles") ?: JSONArray()),
                forecast = row.optJSONObject("forecast")?.let(::parseForecast),
                qualityStatus = quality.optStringClean("status"),
                qualityScore = quality.optDouble("score", 0.0),
            )
        }
        return output.sortedBy { it.symbol }
    }

    private fun parseTicker(row: JSONObject): TickerData =
        TickerData(
            symbol = row.optStringClean("symbol"),
            lastPrice = row.optDouble("last_price", 0.0),
            bid = row.optDouble("bid", 0.0),
            ask = row.optDouble("ask", 0.0),
            turnover24h = row.optDouble("turnover_24h", 0.0),
            volume24h = row.optDouble("volume_24h", 0.0),
            change24h = row.optDouble("change_24h", 0.0),
            spreadPercent = row.optDouble("spread_percent", 0.0),
        )

    private fun parseCandles(items: JSONArray): List<Candle> {
        val output = mutableListOf<Candle>()
        for (index in 0 until items.length()) {
            val row = items.optJSONObject(index) ?: continue
            output += Candle(
                timestamp = row.optLong("timestamp", 0L),
                open = row.optDouble("open", 0.0),
                high = row.optDouble("high", 0.0),
                low = row.optDouble("low", 0.0),
                close = row.optDouble("close", 0.0),
                volume = row.optDouble("volume", 0.0),
                ema50 = row.optDoubleOrNull("ema_50"),
                ema200 = row.optDoubleOrNull("ema_200"),
                rsi14 = row.optDoubleOrNull("rsi_14"),
                atr14 = row.optDoubleOrNull("atr_14"),
                macd = row.optDoubleOrNull("macd"),
                macdSignal = row.optDoubleOrNull("macd_signal"),
            )
        }
        return output
    }

    private fun parseForecast(row: JSONObject): ForecastData {
        val featureItems = row.optJSONArray("feature_snapshot") ?: JSONArray()
        val features = mutableListOf<FeatureItem>()
        for (index in 0 until featureItems.length()) {
            val feature = featureItems.optJSONObject(index) ?: continue
            features += FeatureItem(
                group = feature.optStringClean("group"),
                label = feature.optStringClean("label").ifBlank { feature.optStringClean("name") },
                rawDisplay = feature.optStringClean("raw_display").ifBlank {
                    feature.optStringClean("raw_value")
                },
                modelDisplay = feature.optStringClean("model_display").ifBlank {
                    feature.optStringClean("model_value")
                },
                interpretation = feature.optStringClean("interpretation").ifBlank {
                    feature.optStringClean("description")
                },
            )
        }
        return ForecastData(
            model = row.optStringClean("model"),
            expectedReturnPercent = row.optDouble("expected_return_percent", 0.0),
            probabilityUp = row.optDouble("probability_up", 0.0),
            skill = row.optDouble("skill", 0.0),
            volatilityPercent = row.optDouble("volatility_percent", 0.0),
            horizon = row.optInt("horizon", 0),
            q10Percent = row.optDouble("quantile_10_percent", 0.0),
            q50Percent = row.optDouble("quantile_50_percent", 0.0),
            q90Percent = row.optDouble("quantile_90_percent", 0.0),
            qualityGatePassed = row.optBooleanOrNull("quality_gate_passed"),
            reason = row.optStringClean("reason"),
            features = features,
        )
    }

    private fun parseLatestSignals(items: JSONArray): Map<String, SignalData> {
        val output = linkedMapOf<String, SignalData>()
        for (index in 0 until items.length()) {
            val row = items.optJSONObject(index) ?: continue
            val symbol = row.optStringClean("symbol")
            if (symbol.isBlank() || output.containsKey(symbol)) continue
            val diagnostics = try {
                JSONObject(row.optStringClean("diagnostics_json"))
            } catch (_: Exception) {
                JSONObject()
            }
            output[symbol] = SignalData(
                symbol = symbol,
                action = row.optStringClean("action"),
                confidence = row.optDouble("confidence", 0.0),
                reason = row.optStringClean("reason"),
                diagnostics = diagnostics,
            )
        }
        return output
    }
}

private fun applyAuthHeaders(connection: HttpURLConnection, token: String) {
    val value = token.trim()
    if (value.isBlank()) return
    connection.setRequestProperty("X-TradeBot-Token", value)
    val authorization = when {
        value.startsWith("Basic ", ignoreCase = true) -> value
        value.startsWith("Bearer ", ignoreCase = true) -> value
        ":" in value -> {
            val encoded = Base64.encodeToString(value.toByteArray(StandardCharsets.UTF_8), Base64.NO_WRAP)
            "Basic $encoded"
        }
        else -> "Bearer $value"
    }
    connection.setRequestProperty("Authorization", authorization)
}
