package miner

import (
	"encoding/json"
	"testing"

	"crypto-miner-agent/config"
)

func TestBuildStratumEndpointsPrimaryOnly(t *testing.T) {
	cfg := config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{
		PoolHost: "pool.example.com",
		PoolPort: 3333,
		PoolTLS:  true,
		PoolPass: "secret",
	}}
	eps := buildStratumEndpoints(cfg)
	if len(eps) != 1 {
		t.Fatalf("expected 1 endpoint, got %d", len(eps))
	}
	if eps[0].Host != "pool.example.com" || eps[0].Port != 3333 || !eps[0].TLS || eps[0].Pass != "secret" {
		t.Fatalf("primary endpoint mismatch: %+v", eps[0])
	}
}

func TestBuildStratumEndpointsSkipsInvalidBackups(t *testing.T) {
	cfg := config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{
		PoolHost: "primary.pool",
		PoolPort: 4444,
		BackupPools: []config.BackupPool{
			{Host: "", Port: 5555},
			{Host: "backup.pool", Port: 0},
			{Host: "good.backup", Port: 6666, TLS: true, Pass: "bp"},
		},
	}}
	eps := buildStratumEndpoints(cfg)
	if len(eps) != 2 {
		t.Fatalf("expected primary + 1 valid backup, got %d", len(eps))
	}
	if eps[1].Host != "good.backup" || eps[1].Port != 6666 || !eps[1].TLS || eps[1].Pass != "bp" {
		t.Fatalf("backup endpoint mismatch: %+v", eps[1])
	}
}

func TestMustMarshal(t *testing.T) {
	raw := mustMarshal(map[string]string{"login": "wallet", "pass": "x"})
	var m map[string]string
	if err := json.Unmarshal(raw, &m); err != nil {
		t.Fatal(err)
	}
	if m["login"] != "wallet" || m["pass"] != "x" {
		t.Fatalf("unexpected map: %v", m)
	}
}

func TestStratumMsgLoginRoundTrip(t *testing.T) {
	params := mustMarshal(map[string]interface{}{
		"login": "4AbC...wallet",
		"pass":  "x",
		"rigid": "worker-1",
		"agent": "AetherForge/1.0.0",
	})
	msg := stratumMsg{
		ID:      1,
		JSONRPC: "2.0",
		Method:  "login",
		Params:  params,
	}
	b, err := json.Marshal(msg)
	if err != nil {
		t.Fatal(err)
	}
	var decoded stratumMsg
	if err := json.Unmarshal(b, &decoded); err != nil {
		t.Fatal(err)
	}
	if decoded.Method != "login" || decoded.JSONRPC != "2.0" {
		t.Fatalf("decoded msg: %+v", decoded)
	}
	var p map[string]interface{}
	if err := json.Unmarshal(decoded.Params, &p); err != nil {
		t.Fatal(err)
	}
	if p["login"] != "4AbC...wallet" || p["pass"] != "x" {
		t.Fatalf("params: %v", p)
	}
}

func TestStratumJobUnmarshal(t *testing.T) {
	raw := `{"blob":"aabb","job_id":"j1","target":"ffff","seed_hash":"ccdd","height":12345}`
	var sj stratumJob
	if err := json.Unmarshal([]byte(raw), &sj); err != nil {
		t.Fatal(err)
	}
	if sj.Blob != "aabb" || sj.JobID != "j1" || sj.Target != "ffff" || sj.SeedHash != "ccdd" || sj.Height != 12345 {
		t.Fatalf("job mismatch: %+v", sj)
	}
}

func TestLoginResultUnmarshal(t *testing.T) {
	raw := `{"id":"sess-1","status":"OK","job":{"blob":"deadbeef","job_id":"42","target":"ffffffff","seed_hash":"seed","height":1}}`
	var lr loginResult
	if err := json.Unmarshal([]byte(raw), &lr); err != nil {
		t.Fatal(err)
	}
	if lr.ID != "sess-1" || lr.Status != "OK" || lr.Job == nil || lr.Job.JobID != "42" {
		t.Fatalf("login result mismatch: %+v", lr)
	}
}

func TestSubmitParamsMarshal(t *testing.T) {
	b, err := json.Marshal(submitParams{
		ID:    "sess-1",
		JobID: "42",
		Nonce: "01020304",
		Hash:  "abc123",
	})
	if err != nil {
		t.Fatal(err)
	}
	var m map[string]string
	if err := json.Unmarshal(b, &m); err != nil {
		t.Fatal(err)
	}
	if m["id"] != "sess-1" || m["job_id"] != "42" || m["nonce"] != "01020304" || m["result"] != "abc123" {
		t.Fatalf("submit params field names: %v", m)
	}
}

func TestStratumMsgJobNotification(t *testing.T) {
	params, _ := json.Marshal(stratumJob{
		Blob: "blobhex", JobID: "99", Target: "targethex", SeedHash: "seedhex", Height: 100,
	})
	line := mustMarshal(stratumMsg{Method: "job", Params: params})
	var msg stratumMsg
	if err := json.Unmarshal(line, &msg); err != nil {
		t.Fatal(err)
	}
	if msg.Method != "job" {
		t.Fatalf("method=%q", msg.Method)
	}
	var sj stratumJob
	if err := json.Unmarshal(msg.Params, &sj); err != nil {
		t.Fatal(err)
	}
	if sj.JobID != "99" || sj.Blob != "blobhex" {
		t.Fatalf("job notification: %+v", sj)
	}
}

func TestBuildStratumEndpointsMultipleBackups(t *testing.T) {
	cfg := config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{
		PoolHost: "primary.pool",
		PoolPort: 3333,
		BackupPools: []config.BackupPool{
			{Host: "backup1.pool", Port: 4444, Pass: "a"},
			{Host: "backup2.pool", Port: 5555, TLS: true},
		},
	}}
	eps := buildStratumEndpoints(cfg)
	if len(eps) != 3 {
		t.Fatalf("expected primary + 2 backups, got %d", len(eps))
	}
	if eps[2].Host != "backup2.pool" || eps[2].Port != 5555 || !eps[2].TLS {
		t.Fatalf("third endpoint mismatch: %+v", eps[2])
	}
}

func TestStratumLoginErrorResponse(t *testing.T) {
	line := `{"id":1,"jsonrpc":"2.0","error":{"code":-1,"message":"invalid wallet"}}`
	var loginResp stratumMsg
	if err := json.Unmarshal([]byte(line), &loginResp); err != nil {
		t.Fatal(err)
	}
	if loginResp.Error == nil {
		t.Fatal("expected login error field")
	}
	if loginResp.Result != nil {
		t.Fatal("error response should not carry a result")
	}
}

func TestStratumKeepaliveMsgRoundTrip(t *testing.T) {
	params := mustMarshal(map[string]string{"id": "sess-abc"})
	msg := stratumMsg{ID: 3, JSONRPC: "2.0", Method: "keepalived", Params: params}
	b, err := json.Marshal(msg)
	if err != nil {
		t.Fatal(err)
	}
	var decoded stratumMsg
	if err := json.Unmarshal(b, &decoded); err != nil {
		t.Fatal(err)
	}
	if decoded.Method != "keepalived" || decoded.JSONRPC != "2.0" {
		t.Fatalf("keepalive msg: %+v", decoded)
	}
	var p map[string]string
	if err := json.Unmarshal(decoded.Params, &p); err != nil {
		t.Fatal(err)
	}
	if p["id"] != "sess-abc" {
		t.Fatalf("keepalive params: %v", p)
	}
}

func TestStratumSubmitRequestRoundTrip(t *testing.T) {
	params, err := json.Marshal(submitParams{
		ID: "sess-1", JobID: "42", Nonce: "04030201", Hash: "deadbeef",
	})
	if err != nil {
		t.Fatal(err)
	}
	msg := stratumMsg{ID: 4, JSONRPC: "2.0", Method: "submit", Params: params}
	b, err := json.Marshal(msg)
	if err != nil {
		t.Fatal(err)
	}
	var decoded stratumMsg
	if err := json.Unmarshal(b, &decoded); err != nil {
		t.Fatal(err)
	}
	if decoded.Method != "submit" {
		t.Fatalf("method=%q", decoded.Method)
	}
	var sp submitParams
	if err := json.Unmarshal(decoded.Params, &sp); err != nil {
		t.Fatal(err)
	}
	if sp.ID != "sess-1" || sp.JobID != "42" || sp.Nonce != "04030201" || sp.Hash != "deadbeef" {
		t.Fatalf("submit params: %+v", sp)
	}
}

func TestStratumJobParamsInvalidJSON(t *testing.T) {
	msg := stratumMsg{Method: "job", Params: json.RawMessage(`{"blob":`)}
	var sj stratumJob
	if err := json.Unmarshal(msg.Params, &sj); err == nil {
		t.Fatal("malformed job params should fail unmarshal")
	}
}

func TestStratumLineInvalidJSONIgnored(t *testing.T) {
	line := "not-json\n"
	var msg stratumMsg
	if err := json.Unmarshal([]byte(line), &msg); err == nil {
		t.Fatal("invalid stratum line should not parse as msg")
	}
}
