package report

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

// collectIPFS gathers IPFS daemon and cluster health information.
func collectIPFS() *IPFSReport {
	r := &IPFSReport{}

	// 1. DaemonActive: systemctl is-active orama-ipfs
	{
		ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
		defer cancel()
		if out, err := runCmd(ctx, "systemctl", "is-active", "orama-ipfs"); err == nil {
			r.DaemonActive = strings.TrimSpace(out) == "active"
		}
	}

	// 2. ClusterActive: systemctl is-active orama-ipfs-cluster
	{
		ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
		defer cancel()
		if out, err := runCmd(ctx, "systemctl", "is-active", "orama-ipfs-cluster"); err == nil {
			r.ClusterActive = strings.TrimSpace(out) == "active"
		}
	}

	// 3. SwarmPeerCount: POST /api/v0/swarm/peers
	{
		body, err := ipfsPost("http://localhost:4501/api/v0/swarm/peers")
		if err == nil {
			var resp struct {
				Peers []interface{} `json:"Peers"`
			}
			if err := json.Unmarshal(body, &resp); err == nil {
				r.SwarmPeerCount = len(resp.Peers)
			}
		}
	}

	// 4. ClusterPeerCount: GET /peers (with fallback to /id)
	//    The /peers endpoint does a synchronous round-trip to ALL cluster peers,
	//    so it can be slow if some peers are unreachable (ghost WG entries, etc.).
	//    Use a generous timeout and fall back to /id if /peers times out.
	{
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		if body, err := httpGet(ctx, "http://localhost:9094/peers"); err == nil {
			var peers []interface{}
			if err := json.Unmarshal(body, &peers); err == nil {
				r.ClusterPeerCount = len(peers)
			}
		}
	}
	// Fallback: if /peers returned 0 (timeout or error), try /id which returns
	// cached cluster_peers instantly without contacting other nodes.
	if r.ClusterPeerCount == 0 {
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()
		if body, err := httpGet(ctx, "http://localhost:9094/id"); err == nil {
			var resp struct {
				ClusterPeers []string `json:"cluster_peers"`
			}
			if err := json.Unmarshal(body, &resp); err == nil && len(resp.ClusterPeers) > 0 {
				// cluster_peers includes self, so count is len(cluster_peers)
				r.ClusterPeerCount = len(resp.ClusterPeers)
			}
		}
	}

	// 5. RepoSizeBytes/RepoMaxBytes: POST /api/v0/repo/stat
	{
		body, err := ipfsPost("http://localhost:4501/api/v0/repo/stat")
		if err == nil {
			var resp struct {
				RepoSize   int64 `json:"RepoSize"`
				StorageMax int64 `json:"StorageMax"`
			}
			if err := json.Unmarshal(body, &resp); err == nil {
				r.RepoSizeBytes = resp.RepoSize
				r.RepoMaxBytes = resp.StorageMax
				if resp.StorageMax > 0 && resp.RepoSize > 0 {
					r.RepoUsePct = int(float64(resp.RepoSize) / float64(resp.StorageMax) * 100)
				}
			}
		}
	}

	// 6. KuboVersion: POST /api/v0/version
	{
		body, err := ipfsPost("http://localhost:4501/api/v0/version")
		if err == nil {
			var resp struct {
				Version string `json:"Version"`
			}
			if err := json.Unmarshal(body, &resp); err == nil {
				r.KuboVersion = resp.Version
			}
		}
	}

	// 7. ClusterVersion: GET /id
	{
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()
		if body, err := httpGet(ctx, "http://localhost:9094/id"); err == nil {
			var resp struct {
				Version string `json:"version"`
			}
			if err := json.Unmarshal(body, &resp); err == nil {
				r.ClusterVersion = resp.Version
			}
		}
	}

	// 8. HasSwarmKey: check file existence
	if _, err := os.Stat("/opt/orama/.orama/data/ipfs/repo/swarm.key"); err == nil {
		r.HasSwarmKey = true
	}

	// 9. BootstrapEmpty: POST /api/v0/bootstrap/list
	{
		body, err := ipfsPost("http://localhost:4501/api/v0/bootstrap/list")
		if err == nil {
			var resp struct {
				Peers []interface{} `json:"Peers"`
			}
			if err := json.Unmarshal(body, &resp); err == nil {
				r.BootstrapEmpty = resp.Peers == nil || len(resp.Peers) == 0
			} else {
				// If we got a response but Peers is missing, treat as empty.
				r.BootstrapEmpty = true
			}
		}
	}

	return r
}

// ipfsPost sends a POST request with an empty body to an IPFS API endpoint.
// IPFS uses POST for all API calls. Uses a 3-second timeout.
func ipfsPost(url string) ([]byte, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(nil))
	if err != nil {
		return nil, err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return io.ReadAll(resp.Body)
}
