package models

import (
	"encoding/json"
	"testing"
	"time"
)

func roundTripJSON(t *testing.T, v any) {
	t.Helper()
	b, err := json.Marshal(v)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	ptr := newSameType(v)
	if err := json.Unmarshal(b, ptr); err != nil {
		t.Fatalf("unmarshal: %v\njson: %s", err, string(b))
	}
}

func newSameType(v any) any {
	switch v.(type) {
	case Agent:
		return &Agent{}
	case AgentService:
		return &AgentService{}
	case AgentCapabilities:
		return &AgentCapabilities{}
	case Share:
		return &Share{}
	case HashrateSample:
		return &HashrateSample{}
	case Job:
		return &Job{}
	case BuildRecord:
		return &BuildRecord{}
	default:
		panic("unsupported type")
	}
}

func TestAgentJSONRoundTrip(t *testing.T) {
	now := time.Date(2026, 5, 30, 12, 0, 0, 0, time.UTC)
	cpuFreq := 3200
	cpuMax := 4000
	throttle := true
	cpuTemp := 72
	diskFree := 120.5
	diskTotal := 512.0
	diskPct := 23
	gpuTemp := 65
	gpuUsage := 40
	ssh := true
	defender := true
	rtp := false
	fwDomain := true
	fwPrivate := false
	fwPublic := true
	patchDays := 14
	patch := "2026-05-01"
	pending := 3
	reboot := false
	elevated := true

	agent := Agent{
		ID: "agent-1", Name: "worker-a", Wallet: "48abc", IP: "10.0.0.5",
		Version: "1.0", Status: "online", CPUCores: 8, MemoryGB: 16,
		LastSeen: now, CreatedAt: now.Add(-time.Hour),
		Hashrate15s: 1200, Hashrate1m: 1180, Hashrate15m: 1150,
		SharesTotal: 100, SharesGood: 98, SharesBad: 2,
		CPUUsagePct: 55.5, MemoryUsagePct: 42.0, UptimeSeconds: 3600,
		Notes: "lab node", Tags: []string{"gpu", "windows"},
		Platform: "windows", Arch: "amd64", OSVersion: "10.0.26200",
		Capabilities: &AgentCapabilities{
			HolePunch: true, RemoteAggressive: false, MeshP2P: false,
			AutoSpread: false, ProcessHollowing: false, AIEnabled: true,
		},
		CPUFreqMHz: &cpuFreq, CPUMaxMHz: &cpuMax, CPUThrottle: &throttle,
		CPUTempC: &cpuTemp, DiskFreeGB: &diskFree, DiskTotalGB: &diskTotal,
		DiskFreePct: &diskPct, GPUTempC: &gpuTemp, GPUUsagePct: &gpuUsage,
		SSHAvailable: &ssh, PostureScore: 80,
		DefenderEnabled: &defender, DefenderRTP: &rtp,
		AVProducts: []string{"Windows Defender"},
		FirewallDomain: &fwDomain, FirewallPrivate: &fwPrivate, FirewallPublic: &fwPublic,
		LastPatchDays: &patchDays, LastPatch: &patch, PendingUpdates: &pending,
		RebootPending: &reboot, AgentElevated: &elevated,
		Services: []AgentService{
			{Name: "WinDefend", DisplayName: "Defender", Status: "running", StartType: "auto"},
		},
	}
	roundTripJSON(t, agent)
}

func TestAgentServiceJSONRoundTrip(t *testing.T) {
	roundTripJSON(t, AgentService{
		Name: "sshd", DisplayName: "OpenSSH", Status: "running", StartType: "manual",
	})
}

func TestAgentCapabilitiesJSONRoundTrip(t *testing.T) {
	roundTripJSON(t, AgentCapabilities{
		HolePunch: true, RemoteAggressive: true, MeshP2P: true,
		AutoSpread: true, ProcessHollowing: true, AIEnabled: true,
	})
}

func TestShareJSONRoundTrip(t *testing.T) {
	roundTripJSON(t, Share{
		ID: 42, AgentID: "a1", JobID: "j1", Difficulty: 100000,
		Accepted: true, Hash: "abc", Nonce: "deadbeef", Timestamp: time.Now().UTC(),
	})
}

func TestShareJSONOmitsEmptyError(t *testing.T) {
	s := Share{ID: 1, AgentID: "a", JobID: "j", Accepted: false, Timestamp: time.Now().UTC()}
	b, _ := json.Marshal(s)
	if string(b) != "" && containsField(string(b), "error") {
		// error field should be omitted when empty
		var m map[string]any
		_ = json.Unmarshal(b, &m)
		if _, ok := m["error"]; ok {
			t.Fatal("empty error should be omitted")
		}
	}
}

func containsField(jsonStr, field string) bool {
	var m map[string]any
	if err := json.Unmarshal([]byte(jsonStr), &m); err != nil {
		return false
	}
	_, ok := m[field]
	return ok
}

func TestHashrateSampleJSONRoundTrip(t *testing.T) {
	roundTripJSON(t, HashrateSample{
		ID: 1, AgentID: "a1", Hashrate: 999.5, Timestamp: time.Now().UTC(),
	})
}

func TestJobJSONRoundTrip(t *testing.T) {
	roundTripJSON(t, Job{
		ID: "job-1", Height: 2800000, Difficulty: 500000,
		BlockTemplate: "template", SeedHash: "seed", Target: "target",
		CreatedAt: time.Now().UTC(),
	})
}

func TestBuildRecordJSONRoundTrip(t *testing.T) {
	roundTripJSON(t, BuildRecord{
		ID: "build-1", WorkerName: "w", ServerURL: "https://hub", Wallet: "48x",
		Threads: 4, FileSize: 1024, BundleSize: 2048,
		FilePath: "/data/build.exe", FileName: "build.exe",
		DownloadURL: "/api/v1/builds/build-1/download", Platform: "windows",
		ExtraFiles: []BuildExtraFile{{FileName: "README.txt"}},
		CreatedAt: time.Now().UTC(), Pinned: true,
		PoolHost: "pool.example.com", PoolPort: 3333, PoolTLS: true, PoolPass: "x",
	})
}

func TestAgentMinimalJSON(t *testing.T) {
	var out Agent
	if err := json.Unmarshal([]byte(`{"id":"x","status":"offline"}`), &out); err != nil {
		t.Fatal(err)
	}
	if out.ID != "x" || out.Status != "offline" {
		t.Fatalf("unexpected: %+v", out)
	}
}
