package service

import (
	"errors"
	"testing"

	"github.com/ocdp/cluster-service/internal/domain/entity"
	"github.com/ocdp/cluster-service/internal/domain/repository"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
)

func TestCompareWorkspaceQuotaReportsExceededRequests(t *testing.T) {
	t.Parallel()

	workspace := &entity.Workspace{
		QuotaCPU:    "2",
		QuotaMemory: "4Gi",
		QuotaGPU:    "1",
		QuotaGPUMem: "10000",
	}
	estimate := &repository.ResourceEstimate{
		Requests: repository.ResourceVector{
			CPU:         resource.MustParse("2500m"),
			Memory:      resource.MustParse("3Gi"),
			GPU:         1,
			GPUMemoryMB: 12000,
		},
	}

	result, err := CompareWorkspaceQuota(workspace, estimate)
	if !errors.Is(err, ErrQuotaExceeded) {
		t.Fatalf("expected ErrQuotaExceeded, got %v", err)
	}
	if result == nil || result.Allowed {
		t.Fatalf("expected denied result, got %#v", result)
	}
	if len(result.Exceeded) != 2 {
		t.Fatalf("expected 2 exceeded resources, got %#v", result.Exceeded)
	}
	if result.Exceeded[0].Name != "requests.cpu" {
		t.Fatalf("expected requests.cpu exceeded first, got %#v", result.Exceeded)
	}
	if result.Exceeded[1].Name != "requests.nvidia.com/gpumem" {
		t.Fatalf("expected requests.nvidia.com/gpumem exceeded second, got %#v", result.Exceeded)
	}
}

func TestCompareWorkspaceQuotaUsesLimitsAsEffectiveRequests(t *testing.T) {
	t.Parallel()

	workspace := &entity.Workspace{
		QuotaGPU:    "0",
		QuotaGPUMem: "9999",
	}
	estimate := &repository.ResourceEstimate{
		Limits: repository.ResourceVector{
			GPU:         1,
			GPUMemoryMB: 10000,
		},
	}

	result, err := CompareWorkspaceQuota(workspace, estimate)
	if !errors.Is(err, ErrQuotaExceeded) {
		t.Fatalf("expected ErrQuotaExceeded from limits-only GPU resources, got %v", err)
	}
	if result == nil || len(result.Exceeded) != 2 {
		t.Fatalf("expected gpu and gpumem to be exceeded, got %#v", result)
	}
}

func TestCompareBindingQuotaSubtractsCurrentReleaseFromUsedQuota(t *testing.T) {
	t.Parallel()

	binding := &entity.WorkspaceClusterBinding{
		QuotaCPU:    "1",
		QuotaMemory: "2Gi",
		QuotaGPU:    "1",
		QuotaGPUMem: "10000",
	}
	usage := &repository.ResourceQuotaUsage{
		Used: repository.ResourceVector{
			CPU:         resource.MustParse("1"),
			Memory:      resource.MustParse("2Gi"),
			GPU:         1,
			GPUMemoryMB: 10000,
		},
	}
	current := &repository.ResourceEstimate{
		Requests: repository.ResourceVector{
			CPU:         resource.MustParse("1"),
			Memory:      resource.MustParse("2Gi"),
			GPU:         1,
			GPUMemoryMB: 10000,
		},
	}
	targetSameSize := &repository.ResourceEstimate{
		Requests: repository.ResourceVector{
			CPU:         resource.MustParse("1"),
			Memory:      resource.MustParse("2Gi"),
			GPU:         1,
			GPUMemoryMB: 10000,
		},
	}

	result, err := CompareBindingQuota(binding, usage, targetSameSize, current)
	if err != nil {
		t.Fatalf("expected update with same resource footprint to fit quota, got %v", err)
	}
	if result.Required.Requests.GPU != 1 || result.Required.Requests.GPUMemoryMB != 10000 {
		t.Fatalf("expected required resources to subtract current release before target, got %#v", result.Required.Requests)
	}

	targetScaledUp := &repository.ResourceEstimate{
		Requests: repository.ResourceVector{
			CPU:         resource.MustParse("2"),
			Memory:      resource.MustParse("4Gi"),
			GPU:         2,
			GPUMemoryMB: 20000,
		},
	}
	result, err = CompareBindingQuota(binding, usage, targetScaledUp, current)
	if !errors.Is(err, ErrQuotaExceeded) {
		t.Fatalf("expected scale-up beyond quota to be rejected, got %v", err)
	}
	if result == nil || result.Allowed {
		t.Fatalf("expected denied quota result, got %#v", result)
	}
}

func TestCompareBindingQuotaTreatsExplicitZeroGPUAsNoGPUAllowed(t *testing.T) {
	t.Parallel()

	binding := &entity.WorkspaceClusterBinding{
		QuotaCPU:    "8",
		QuotaMemory: "32Gi",
		QuotaGPU:    "0",
		QuotaGPUMem: "0",
	}
	vllmLikeEstimate := &repository.ResourceEstimate{
		Requests: repository.ResourceVector{
			CPU:         resource.MustParse("2"),
			Memory:      resource.MustParse("8Gi"),
			GPU:         1,
			GPUMemoryMB: 10000,
		},
	}

	result, err := CompareBindingQuota(binding, &repository.ResourceQuotaUsage{}, vllmLikeEstimate, nil)
	if !errors.Is(err, ErrQuotaExceeded) {
		t.Fatalf("expected GPU request to exceed explicit zero quota, got %v", err)
	}
	exceeded := map[string]bool{}
	for _, item := range result.Exceeded {
		exceeded[item.Name] = true
	}
	for _, name := range []string{"requests.nvidia.com/gpu", "requests.nvidia.com/gpumem"} {
		if !exceeded[name] {
			t.Fatalf("expected %s to be exceeded, got %#v", name, result.Exceeded)
		}
	}
}

func TestBindingQuotaHardKeepsGPUMemoryAsIntegerMB(t *testing.T) {
	t.Parallel()

	hard := bindingQuotaHard(&entity.WorkspaceClusterBinding{QuotaGPU: "1", QuotaGPUMem: "10000"})
	gpuMem := hard[corev1.ResourceName("requests.nvidia.com/gpumem")]
	if gpuMem.Value() != 10000 {
		t.Fatalf("expected gpumem quota to remain integer MB 10000, got %s value=%d", gpuMem.String(), gpuMem.Value())
	}
}

func TestEstimateRenderedManifestResourcesSumsPodTemplates(t *testing.T) {
	t.Parallel()

	manifest := `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-worker
spec:
  replicas: 3
  template:
    spec:
      initContainers:
      - name: init
        image: busybox
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
      containers:
      - name: app
        image: busybox
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
            nvidia.com/gpu: "1"
            nvidia.com/gpumem: "10000"
          limits:
            cpu: "1"
            memory: 2Gi
            nvidia.com/gpu: "1"
            nvidia.com/gpumem: "12000"
---
apiVersion: v1
kind: Service
metadata:
  name: ignored
`
	estimate, err := EstimateRenderedManifestResources(manifest)
	if err != nil {
		t.Fatalf("EstimateRenderedManifestResources returned error: %v", err)
	}
	if estimate.Requests.CPU.Cmp(resource.MustParse("1800m")) != 0 {
		t.Fatalf("expected requests cpu 1800m, got %s", estimate.Requests.CPU.String())
	}
	if estimate.Requests.Memory.Cmp(resource.MustParse("3456Mi")) != 0 {
		t.Fatalf("expected requests memory 3456Mi, got %s", estimate.Requests.Memory.String())
	}
	if estimate.Requests.GPU != 3 {
		t.Fatalf("expected requests gpu 3, got %d", estimate.Requests.GPU)
	}
	if estimate.Requests.GPUMemoryMB != 30000 {
		t.Fatalf("expected requests gpumem 30000, got %d", estimate.Requests.GPUMemoryMB)
	}
	if estimate.Limits.CPU.Cmp(resource.MustParse("3")) != 0 {
		t.Fatalf("expected limits cpu 3, got %s", estimate.Limits.CPU.String())
	}
	if estimate.Limits.Memory.Cmp(resource.MustParse("6Gi")) != 0 {
		t.Fatalf("expected limits memory 6Gi, got %s", estimate.Limits.Memory.String())
	}
	if estimate.Limits.GPU != 3 {
		t.Fatalf("expected limits gpu 3, got %d", estimate.Limits.GPU)
	}
	if estimate.Limits.GPUMemoryMB != 36000 {
		t.Fatalf("expected limits gpumem 36000, got %d", estimate.Limits.GPUMemoryMB)
	}
}
