package checks

import (
	"fmt"

	"github.com/DeBrosOfficial/network/pkg/inspector"
)

func init() {
	inspector.RegisterChecker("ipfs", CheckIPFS)
}

const ipfsSub = "ipfs"

// CheckIPFS runs all IPFS health checks against cluster data.
func CheckIPFS(data *inspector.ClusterData) []inspector.CheckResult {
	var results []inspector.CheckResult

	for _, nd := range data.Nodes {
		if nd.IPFS == nil {
			continue
		}
		results = append(results, checkIPFSPerNode(nd, data)...)
	}

	results = append(results, checkIPFSCrossNode(data)...)

	return results
}

func checkIPFSPerNode(nd *inspector.NodeData, data *inspector.ClusterData) []inspector.CheckResult {
	var r []inspector.CheckResult
	ipfs := nd.IPFS
	node := nd.Node.Name()

	// 3.1 IPFS daemon running
	if ipfs.DaemonActive {
		r = append(r, inspector.Pass("ipfs.daemon_active", "IPFS daemon active", ipfsSub, node,
			"orama-ipfs is active", inspector.Critical))
	} else {
		r = append(r, inspector.Fail("ipfs.daemon_active", "IPFS daemon active", ipfsSub, node,
			"orama-ipfs is not active", inspector.Critical))
		return r
	}

	// 3.2 IPFS Cluster running
	if ipfs.ClusterActive {
		r = append(r, inspector.Pass("ipfs.cluster_active", "IPFS Cluster active", ipfsSub, node,
			"orama-ipfs-cluster is active", inspector.Critical))
	} else {
		r = append(r, inspector.Fail("ipfs.cluster_active", "IPFS Cluster active", ipfsSub, node,
			"orama-ipfs-cluster is not active", inspector.Critical))
	}

	// 3.6 Swarm peer count
	expectedNodes := countIPFSNodes(data)
	if ipfs.SwarmPeerCount >= 0 {
		expectedPeers := expectedNodes - 1
		if expectedPeers < 0 {
			expectedPeers = 0
		}
		if ipfs.SwarmPeerCount >= expectedPeers {
			r = append(r, inspector.Pass("ipfs.swarm_peers", "Swarm peer count sufficient", ipfsSub, node,
				fmt.Sprintf("peers=%d (expected >=%d)", ipfs.SwarmPeerCount, expectedPeers), inspector.High))
		} else if ipfs.SwarmPeerCount > 0 {
			r = append(r, inspector.Warn("ipfs.swarm_peers", "Swarm peer count sufficient", ipfsSub, node,
				fmt.Sprintf("peers=%d (expected >=%d)", ipfs.SwarmPeerCount, expectedPeers), inspector.High))
		} else {
			r = append(r, inspector.Fail("ipfs.swarm_peers", "Swarm peer count sufficient", ipfsSub, node,
				fmt.Sprintf("peers=%d (isolated!)", ipfs.SwarmPeerCount), inspector.Critical))
		}
	}

	// 3.12 Cluster peer count
	if ipfs.ClusterPeerCount >= 0 {
		if ipfs.ClusterPeerCount >= expectedNodes {
			r = append(r, inspector.Pass("ipfs.cluster_peers", "Cluster peer count matches expected", ipfsSub, node,
				fmt.Sprintf("cluster_peers=%d (expected=%d)", ipfs.ClusterPeerCount, expectedNodes), inspector.Critical))
		} else {
			r = append(r, inspector.Warn("ipfs.cluster_peers", "Cluster peer count matches expected", ipfsSub, node,
				fmt.Sprintf("cluster_peers=%d (expected=%d)", ipfs.ClusterPeerCount, expectedNodes), inspector.Critical))
		}
	}

	// 3.14 Cluster peer errors
	if ipfs.ClusterErrors == 0 {
		r = append(r, inspector.Pass("ipfs.cluster_errors", "No cluster peer errors", ipfsSub, node,
			"all cluster peers healthy", inspector.Critical))
	} else {
		r = append(r, inspector.Fail("ipfs.cluster_errors", "No cluster peer errors", ipfsSub, node,
			fmt.Sprintf("%d peers reporting errors", ipfs.ClusterErrors), inspector.Critical))
	}

	// 3.20 Repo size vs max
	if ipfs.RepoMaxBytes > 0 && ipfs.RepoSizeBytes > 0 {
		pct := float64(ipfs.RepoSizeBytes) / float64(ipfs.RepoMaxBytes) * 100
		sizeMB := ipfs.RepoSizeBytes / (1024 * 1024)
		maxMB := ipfs.RepoMaxBytes / (1024 * 1024)
		if pct < 80 {
			r = append(r, inspector.Pass("ipfs.repo_size", "Repo size below limit", ipfsSub, node,
				fmt.Sprintf("repo=%dMB/%dMB (%.0f%%)", sizeMB, maxMB, pct), inspector.High))
		} else if pct < 95 {
			r = append(r, inspector.Warn("ipfs.repo_size", "Repo size below limit", ipfsSub, node,
				fmt.Sprintf("repo=%dMB/%dMB (%.0f%%)", sizeMB, maxMB, pct), inspector.High))
		} else {
			r = append(r, inspector.Fail("ipfs.repo_size", "Repo size below limit", ipfsSub, node,
				fmt.Sprintf("repo=%dMB/%dMB (%.0f%% NEARLY FULL)", sizeMB, maxMB, pct), inspector.Critical))
		}
	}

	// 3.3 Version
	if ipfs.KuboVersion != "" && ipfs.KuboVersion != "unknown" {
		r = append(r, inspector.Pass("ipfs.kubo_version", "Kubo version reported", ipfsSub, node,
			fmt.Sprintf("kubo=%s", ipfs.KuboVersion), inspector.Low))
	}
	if ipfs.ClusterVersion != "" && ipfs.ClusterVersion != "unknown" {
		r = append(r, inspector.Pass("ipfs.cluster_version", "Cluster version reported", ipfsSub, node,
			fmt.Sprintf("cluster=%s", ipfs.ClusterVersion), inspector.Low))
	}

	// 3.29 Swarm key exists (private swarm)
	if ipfs.HasSwarmKey {
		r = append(r, inspector.Pass("ipfs.swarm_key", "Swarm key exists (private swarm)", ipfsSub, node,
			"swarm.key present", inspector.Critical))
	} else {
		r = append(r, inspector.Fail("ipfs.swarm_key", "Swarm key exists (private swarm)", ipfsSub, node,
			"swarm.key NOT found", inspector.Critical))
	}

	// 3.30 Bootstrap empty (private swarm)
	if ipfs.BootstrapEmpty {
		r = append(r, inspector.Pass("ipfs.bootstrap_empty", "Bootstrap list empty (private)", ipfsSub, node,
			"no public bootstrap peers", inspector.High))
	} else {
		r = append(r, inspector.Warn("ipfs.bootstrap_empty", "Bootstrap list empty (private)", ipfsSub, node,
			"bootstrap list is not empty (should be empty for private swarm)", inspector.High))
	}

	return r
}

func checkIPFSCrossNode(data *inspector.ClusterData) []inspector.CheckResult {
	var r []inspector.CheckResult

	type nodeInfo struct {
		name string
		ipfs *inspector.IPFSData
	}
	var nodes []nodeInfo
	for _, nd := range data.Nodes {
		if nd.IPFS != nil && nd.IPFS.DaemonActive {
			nodes = append(nodes, nodeInfo{name: nd.Node.Name(), ipfs: nd.IPFS})
		}
	}

	if len(nodes) < 2 {
		return r
	}

	// Version consistency
	kuboVersions := map[string][]string{}
	clusterVersions := map[string][]string{}
	for _, n := range nodes {
		if n.ipfs.KuboVersion != "" && n.ipfs.KuboVersion != "unknown" {
			kuboVersions[n.ipfs.KuboVersion] = append(kuboVersions[n.ipfs.KuboVersion], n.name)
		}
		if n.ipfs.ClusterVersion != "" && n.ipfs.ClusterVersion != "unknown" {
			clusterVersions[n.ipfs.ClusterVersion] = append(clusterVersions[n.ipfs.ClusterVersion], n.name)
		}
	}

	if len(kuboVersions) == 1 {
		for v := range kuboVersions {
			r = append(r, inspector.Pass("ipfs.kubo_version_consistent", "Kubo version consistent", ipfsSub, "",
				fmt.Sprintf("version=%s across %d nodes", v, len(nodes)), inspector.Medium))
		}
	} else if len(kuboVersions) > 1 {
		r = append(r, inspector.Warn("ipfs.kubo_version_consistent", "Kubo version consistent", ipfsSub, "",
			fmt.Sprintf("%d different versions", len(kuboVersions)), inspector.Medium))
	}

	if len(clusterVersions) == 1 {
		for v := range clusterVersions {
			r = append(r, inspector.Pass("ipfs.cluster_version_consistent", "Cluster version consistent", ipfsSub, "",
				fmt.Sprintf("version=%s across %d nodes", v, len(nodes)), inspector.Medium))
		}
	} else if len(clusterVersions) > 1 {
		r = append(r, inspector.Warn("ipfs.cluster_version_consistent", "Cluster version consistent", ipfsSub, "",
			fmt.Sprintf("%d different versions", len(clusterVersions)), inspector.Medium))
	}

	// Repo size convergence
	var sizes []int64
	for _, n := range nodes {
		if n.ipfs.RepoSizeBytes > 0 {
			sizes = append(sizes, n.ipfs.RepoSizeBytes)
		}
	}
	if len(sizes) >= 2 {
		minSize, maxSize := sizes[0], sizes[0]
		for _, s := range sizes[1:] {
			if s < minSize {
				minSize = s
			}
			if s > maxSize {
				maxSize = s
			}
		}
		if minSize > 0 {
			ratio := float64(maxSize) / float64(minSize)
			if ratio <= 2.0 {
				r = append(r, inspector.Pass("ipfs.repo_convergence", "Repo size similar across nodes", ipfsSub, "",
					fmt.Sprintf("ratio=%.1fx", ratio), inspector.Medium))
			} else {
				r = append(r, inspector.Warn("ipfs.repo_convergence", "Repo size similar across nodes", ipfsSub, "",
					fmt.Sprintf("ratio=%.1fx (diverged)", ratio), inspector.Medium))
			}
		}
	}

	return r
}

func countIPFSNodes(data *inspector.ClusterData) int {
	count := 0
	for _, nd := range data.Nodes {
		if nd.IPFS != nil {
			count++
		}
	}
	return count
}
