package track1

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
)

// RPCProbeResult is one JSON-RPC health check (URLs are redacted to origin only in JSON).
type RPCProbeResult struct {
	Name            string  `json:"name"`
	ChainKey        string  `json:"chainKey,omitempty"`
	Endpoint        string  `json:"endpoint"`
	OK              bool    `json:"ok"`
	LatencyMs       int64   `json:"latencyMs"`
	BlockNumber     string  `json:"blockNumber,omitempty"`
	BlockNumberDec  string  `json:"blockNumberDec,omitempty"`
	HeadAgeSeconds  float64 `json:"headAgeSeconds,omitempty"`
	Error           string  `json:"error,omitempty"`
}

type jsonRPCReq struct {
	JSONRPC string        `json:"jsonrpc"`
	Method  string        `json:"method"`
	Params  []interface{} `json:"params"`
	ID      int           `json:"id"`
}

type jsonRPCResp struct {
	Result json.RawMessage `json:"result"`
	Error  *struct {
		Message string `json:"message"`
	} `json:"error"`
}

func redactRPCOrigin(raw string) string {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return ""
	}
	u, err := url.Parse(raw)
	if err != nil || u.Host == "" {
		return "hidden"
	}
	if u.Scheme == "" {
		return u.Host
	}
	return u.Scheme + "://" + u.Host
}

func postJSONRPC(ctx context.Context, client *http.Client, rpcURL string, method string, params []interface{}) (json.RawMessage, int64, error) {
	if client == nil {
		client = http.DefaultClient
	}
	body, err := json.Marshal(jsonRPCReq{JSONRPC: "2.0", Method: method, Params: params, ID: 1})
	if err != nil {
		return nil, 0, err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, rpcURL, bytes.NewReader(body))
	if err != nil {
		return nil, 0, err
	}
	req.Header.Set("Content-Type", "application/json")

	start := time.Now()
	resp, err := client.Do(req)
	latency := time.Since(start).Milliseconds()
	if err != nil {
		return nil, latency, err
	}
	defer resp.Body.Close()
	b, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
	if err != nil {
		return nil, latency, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, latency, fmt.Errorf("http %d", resp.StatusCode)
	}
	var out jsonRPCResp
	if err := json.Unmarshal(b, &out); err != nil {
		return nil, latency, err
	}
	if out.Error != nil && out.Error.Message != "" {
		return nil, latency, fmt.Errorf("rpc error: %s", out.Error.Message)
	}
	return out.Result, latency, nil
}

// ProbeEVMJSONRPC runs eth_blockNumber and eth_getBlockByNumber(latest) for head age.
func ProbeEVMJSONRPC(ctx context.Context, name, chainKey, rpcURL string) RPCProbeResult {
	rpcURL = strings.TrimSpace(rpcURL)
	res := RPCProbeResult{
		Name:     name,
		ChainKey: chainKey,
		Endpoint: redactRPCOrigin(rpcURL),
	}
	if rpcURL == "" {
		res.Error = "empty rpc url"
		return res
	}
	client := &http.Client{Timeout: 6 * time.Second}

	numRaw, lat1, err := postJSONRPC(ctx, client, rpcURL, "eth_blockNumber", []interface{}{})
	if err != nil {
		res.LatencyMs = lat1
		res.Error = err.Error()
		return res
	}
	var numHex string
	if err := json.Unmarshal(numRaw, &numHex); err != nil {
		res.LatencyMs = lat1
		res.Error = "blockNumber decode: " + err.Error()
		return res
	}
	res.BlockNumber = numHex
	if n, err := strconv.ParseInt(strings.TrimPrefix(strings.TrimSpace(numHex), "0x"), 16, 64); err == nil {
		res.BlockNumberDec = strconv.FormatInt(n, 10)
	}

	blockRaw, lat2, err := postJSONRPC(ctx, client, rpcURL, "eth_getBlockByNumber", []interface{}{"latest", false})
	res.LatencyMs = lat1 + lat2
	if err != nil {
		res.OK = true
		res.Error = "head block timestamp unavailable: " + err.Error()
		return res
	}
	var block struct {
		Timestamp string `json:"timestamp"`
	}
	if err := json.Unmarshal(blockRaw, &block); err != nil || block.Timestamp == "" {
		res.OK = true
		if err != nil {
			res.Error = "block decode: " + err.Error()
		}
		return res
	}
	tsHex := strings.TrimSpace(block.Timestamp)
	ts, err := strconv.ParseInt(strings.TrimPrefix(tsHex, "0x"), 16, 64)
	if err != nil {
		res.OK = true
		res.Error = "timestamp parse: " + err.Error()
		return res
	}
	bt := time.Unix(ts, 0)
	res.HeadAgeSeconds = time.Since(bt).Seconds()
	res.OK = true
	return res
}

func readOptionalVerifyJSON() map[string]interface{} {
	path := strings.TrimSpace(os.Getenv("MISSION_CONTROL_VERIFY_JSON"))
	if path == "" {
		return nil
	}
	b, err := os.ReadFile(path)
	if err != nil || len(b) == 0 {
		return map[string]interface{}{"error": "unreadable or empty", "path": path}
	}
	if len(b) > 512*1024 {
		return map[string]interface{}{"error": "file too large", "path": path}
	}
	var v map[string]interface{}
	if err := json.Unmarshal(b, &v); err != nil {
		return map[string]interface{}{"error": err.Error(), "path": path}
	}
	return v
}

// ParseExtraRPCProbes reads MISSION_CONTROL_EXTRA_RPCS lines "name|url" or "name|url|chainKey".
func ParseExtraRPCProbes() [][3]string {
	raw := strings.TrimSpace(os.Getenv("MISSION_CONTROL_EXTRA_RPCS"))
	if raw == "" {
		return nil
	}
	var out [][3]string
	for _, line := range strings.Split(raw, "\n") {
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		parts := strings.Split(line, "|")
		if len(parts) < 2 {
			continue
		}
		name := strings.TrimSpace(parts[0])
		u := strings.TrimSpace(parts[1])
		ck := ""
		if len(parts) > 2 {
			ck = strings.TrimSpace(parts[2])
		}
		if name != "" && u != "" {
			out = append(out, [3]string{name, u, ck})
		}
	}
	return out
}
