package collector

import (
	"context"
	"encoding/json"
	"net"
	"net/http"
	"os"
	"time"

	"host-agent/internal/config"

	psnet "github.com/shirou/gopsutil/v3/net"
	"github.com/shirou/gopsutil/v3/process"
)

// dockerSock는 Docker 데몬 유닉스 소켓 경로입니다(환경변수 DOCKER_SOCK로 변경 가능).
func dockerSock() string {
	if s := os.Getenv("DOCKER_SOCK"); s != "" {
		return s
	}
	return "/var/run/docker.sock"
}

// dockerHTTPClient는 유닉스 소켓을 통해 Docker Engine API를 호출하는 경량 클라이언트입니다.
// (무거운 Docker SDK 대신 소켓에 직접 HTTP 호출 → 의존성/메모리 최소화)
func dockerHTTPClient() *http.Client {
	sock := dockerSock()
	return &http.Client{
		Timeout: 4 * time.Second,
		Transport: &http.Transport{
			DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
				return (&net.Dialer{Timeout: 2 * time.Second}).DialContext(ctx, "unix", sock)
			},
		},
	}
}

// dockerStats는 컨테이너의 상태/리소스 지표를 수집합니다.
// 반환: (지표맵, 컨테이너상태[running|exited|...], 이미지명). 실패 시 (nil, "", "").
func dockerStats(name string) (map[string]interface{}, string, string) {
	client := dockerHTTPClient()

	// 1) 컨테이너 메타/상태
	var meta struct {
		State struct {
			Status  string `json:"Status"`
			Running bool   `json:"Running"`
			Health  struct {
				Status string `json:"Status"`
			} `json:"Health"`
		} `json:"State"`
		Config struct {
			Image string `json:"Image"`
		} `json:"Config"`
		RestartCount int `json:"RestartCount"`
	}
	if err := dockerGet(client, "/containers/"+name+"/json", &meta); err != nil {
		return nil, "", ""
	}

	m := map[string]interface{}{
		"status":   meta.State.Status,
		"restarts": meta.RestartCount,
	}
	if meta.State.Health.Status != "" {
		m["health"] = meta.State.Health.Status
	}

	// 2) 리소스 stats (정지 상태면 생략)
	if meta.State.Running {
		var st dockerStatsJSON
		if err := dockerGet(client, "/containers/"+name+"/stats?stream=false", &st); err == nil {
			m["cpu_pct"] = roundF(st.cpuPercent(), 1)
			used := st.MemoryStats.Usage
			limit := st.MemoryStats.Limit
			m["mem_bytes"] = used
			if limit > 0 {
				m["mem_pct"] = roundF(float64(used)/float64(limit)*100, 1)
			}
		}
	}

	return m, meta.State.Status, meta.Config.Image
}

// dockerStatsJSON은 /stats 응답 중 필요한 필드만 추립니다.
type dockerStatsJSON struct {
	CPUStats struct {
		CPUUsage struct {
			TotalUsage  uint64   `json:"total_usage"`
			PercpuUsage []uint64 `json:"percpu_usage"`
		} `json:"cpu_usage"`
		SystemUsage uint64 `json:"system_cpu_usage"`
		OnlineCPUs  uint32 `json:"online_cpus"`
	} `json:"cpu_stats"`
	PreCPUStats struct {
		CPUUsage struct {
			TotalUsage uint64 `json:"total_usage"`
		} `json:"cpu_usage"`
		SystemUsage uint64 `json:"system_cpu_usage"`
	} `json:"precpu_stats"`
	MemoryStats struct {
		Usage uint64 `json:"usage"`
		Limit uint64 `json:"limit"`
	} `json:"memory_stats"`
}

// cpuPercent는 Docker 표준 방식으로 컨테이너 CPU 사용률(%)을 계산합니다(best-effort).
func (s dockerStatsJSON) cpuPercent() float64 {
	cpuDelta := float64(s.CPUStats.CPUUsage.TotalUsage) - float64(s.PreCPUStats.CPUUsage.TotalUsage)
	sysDelta := float64(s.CPUStats.SystemUsage) - float64(s.PreCPUStats.SystemUsage)
	cpus := float64(s.CPUStats.OnlineCPUs)
	if cpus == 0 {
		cpus = float64(len(s.CPUStats.CPUUsage.PercpuUsage))
	}
	if cpus == 0 {
		cpus = 1
	}
	if cpuDelta > 0 && sysDelta > 0 {
		return (cpuDelta / sysDelta) * cpus * 100
	}
	return 0
}

func dockerGet(client *http.Client, path string, out interface{}) error {
	resp, err := client.Get("http://docker" + path)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	return json.NewDecoder(resp.Body).Decode(out)
}

// nativeProcStats는 native 서비스의 프로세스 리소스(CPU%/MEM)를 수집합니다.
// 설정의 port(리슨 포트) 또는 process(프로세스명)로 PID를 찾습니다. 못 찾으면 nil.
func nativeProcStats(cfg config.ServiceConfig) map[string]interface{} {
	pid := findPID(cfg)
	if pid <= 0 {
		return nil
	}
	p, err := process.NewProcess(int32(pid))
	if err != nil {
		return nil
	}
	m := map[string]interface{}{"pid": pid}
	if cp, err := p.CPUPercent(); err == nil {
		m["cpu_pct"] = roundF(cp, 1)
	}
	if mi, err := p.MemoryInfo(); err == nil && mi != nil {
		m["mem_bytes"] = mi.RSS
	}
	return m
}

// findPID는 리슨 포트 또는 프로세스명으로 PID를 찾습니다(best-effort).
func findPID(cfg config.ServiceConfig) int {
	if cfg.Port > 0 {
		if conns, err := psnet.Connections("inet"); err == nil {
			for _, c := range conns {
				if c.Laddr.Port == uint32(cfg.Port) && c.Pid > 0 &&
					(c.Status == "LISTEN" || c.Status == "NONE" || c.Status == "") {
					return int(c.Pid)
				}
			}
		}
	}
	if cfg.Process != "" {
		if ps, err := process.Processes(); err == nil {
			for _, p := range ps {
				if n, err := p.Name(); err == nil && n == cfg.Process {
					return int(p.Pid)
				}
			}
		}
	}
	return 0
}
