package service

import (
	"context"
	"strings"
	"testing"

	"oci-portal/internal/oci"
)

func TestCreateInstanceValidation(t *testing.T) {
	valid := oci.CreateInstanceInput{
		AvailabilityDomain: "AD-1",
		DisplayName:        "vm1",
		Shape:              "VM.Standard.A1.Flex",
		ImageID:            "ocid1.image..a",
		SubnetID:           "ocid1.subnet..s",
	}
	tests := []struct {
		name    string
		mutate  func(*oci.CreateInstanceInput)
		wantErr string
	}{
		{name: "合法输入", mutate: func(*oci.CreateInstanceInput) {}},
		{name: "缺名称", mutate: func(in *oci.CreateInstanceInput) { in.DisplayName = "" }, wantErr: "displayName"},
		{name: "可用域可空(默认 ad-1)", mutate: func(in *oci.CreateInstanceInput) { in.AvailabilityDomain = "" }},
		{name: "缺 shape", mutate: func(in *oci.CreateInstanceInput) { in.Shape = "" }, wantErr: "shape"},
		{name: "缺启动源", mutate: func(in *oci.CreateInstanceInput) { in.ImageID = "" }, wantErr: "imageId or bootVolumeId"},
		{name: "启动源互斥", mutate: func(in *oci.CreateInstanceInput) { in.BootVolumeID = "bv" }, wantErr: "mutually exclusive"},
		{name: "子网可空(自动建网)", mutate: func(in *oci.CreateInstanceInput) { in.SubnetID = "" }},
		{name: "密钥与密码互斥", mutate: func(in *oci.CreateInstanceInput) {
			in.SSHPublicKey = "ssh-ed25519 AAA"
			in.RootPassword = "pw"
		}, wantErr: "sshPublicKey and rootPassword"},
		{name: "随机密码与密钥互斥", mutate: func(in *oci.CreateInstanceInput) {
			in.GenerateRootPassword = true
			in.SSHPublicKey = "ssh-ed25519 AAA"
		}, wantErr: "generateRootPassword"},
		{name: "随机密码与固定密码互斥", mutate: func(in *oci.CreateInstanceInput) {
			in.GenerateRootPassword = true
			in.RootPassword = "pw"
		}, wantErr: "generateRootPassword"},
	}
	svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}})
	cfg := importAliveConfig(t, svc)
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			in := valid
			tt.mutate(&in)
			_, _, err := svc.CreateInstances(context.Background(), cfg.ID, in, 1)
			if tt.wantErr == "" {
				if err != nil {
					t.Errorf("CreateInstances: %v, want success", err)
				}
				return
			}
			if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
				t.Errorf("CreateInstances error = %v, want contains %q", err, tt.wantErr)
			}
		})
	}
}

func TestCreateInstancesBatch(t *testing.T) {
	client := &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}
	svc := newTestService(t, client)
	cfg := importAliveConfig(t, svc)
	in := oci.CreateInstanceInput{DisplayName: "vm", Shape: "VM.Standard.A1.Flex", ImageID: "img"}

	for _, count := range []int{0, 11} {
		if _, _, err := svc.CreateInstances(context.Background(), cfg.ID, in, count); err == nil {
			t.Errorf("count=%d: got nil error, want out-of-range failure", count)
		}
	}

	instances, failures, err := svc.CreateInstances(context.Background(), cfg.ID, in, 3)
	if err != nil {
		t.Fatalf("CreateInstances: %v", err)
	}
	if len(failures) != 0 {
		t.Errorf("failures = %v, want none", failures)
	}
	got := make([]string, 0, len(instances))
	for _, inst := range instances {
		got = append(got, inst.DisplayName)
	}
	want := []string{"vm-1", "vm-2", "vm-3"}
	if len(got) != len(want) {
		t.Fatalf("created %d instances %v, want %v", len(got), got, want)
	}
	for i := range want {
		if got[i] != want[i] {
			t.Errorf("instance %d name = %q, want %q", i, got[i], want[i])
		}
	}
}

func TestCreateInstancesGeneratedRootPassword(t *testing.T) {
	client := &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}
	svc := newTestService(t, client)
	cfg := importAliveConfig(t, svc)
	in := oci.CreateInstanceInput{
		DisplayName:          "vm",
		Shape:                "VM.Standard.A1.Flex",
		ImageID:              "img",
		GenerateRootPassword: true,
		FreeformTags:         map[string]string{"env": "prod"},
	}

	if _, _, err := svc.CreateInstances(context.Background(), cfg.ID, in, 2); err != nil {
		t.Fatalf("CreateInstances: %v", err)
	}
	if len(client.launched) != 2 {
		t.Fatalf("launched %d instances, want 2", len(client.launched))
	}
	pwds := map[string]bool{}
	for i, got := range client.launched {
		if got.RootPassword == "" {
			t.Errorf("instance %d: RootPassword empty, want generated", i)
		}
		if got.FreeformTags["RootPassword"] != got.RootPassword {
			t.Errorf("instance %d: tag RootPassword = %q, want %q", i, got.FreeformTags["RootPassword"], got.RootPassword)
		}
		if got.FreeformTags["env"] != "prod" {
			t.Errorf("instance %d: tag env = %q, want prod (原有标签须保留)", i, got.FreeformTags["env"])
		}
		pwds[got.RootPassword] = true
	}
	if len(pwds) != 2 {
		t.Errorf("批量创建密码应各台独立, got %d 个不同密码", len(pwds))
	}
	if in.FreeformTags["RootPassword"] != "" {
		t.Error("原始输入 FreeformTags 被修改, 应复制后再写入")
	}
}

func TestUpdateInstanceRequiresChange(t *testing.T) {
	svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}})
	cfg := importAliveConfig(t, svc)
	if _, err := svc.UpdateInstance(context.Background(), cfg.ID, "ocid1.instance..i", oci.UpdateInstanceInput{}); err == nil {
		t.Error("UpdateInstance with empty input: got nil error, want failure")
	}
}

func TestCreateConsoleConnectionValidation(t *testing.T) {
	svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}})
	cfg := importAliveConfig(t, svc)
	tests := []struct {
		name string
		key  string
	}{
		{name: "空公钥", key: ""},
		{name: "非 RSA 公钥", key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA test"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if _, err := svc.CreateConsoleConnection(context.Background(), cfg.ID, "", "ocid1.instance..i", tt.key); err == nil {
				t.Error("CreateConsoleConnection: got nil error, want failure")
			}
		})
	}
}

func TestDeleteInstanceIpv6RequiresAddress(t *testing.T) {
	svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}})
	cfg := importAliveConfig(t, svc)
	if err := svc.DeleteInstanceIpv6(context.Background(), cfg.ID, "", "ocid1.instance..i", ""); err == nil {
		t.Error("DeleteInstanceIpv6 with empty address: got nil error, want failure")
	}
}

func TestUpdateBootVolumeRequiresChange(t *testing.T) {
	svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}})
	cfg := importAliveConfig(t, svc)
	if _, err := svc.UpdateBootVolume(context.Background(), cfg.ID, "ocid1.bootvolume..b", oci.UpdateBootVolumeInput{}); err == nil {
		t.Error("UpdateBootVolume with empty input: got nil error, want failure")
	}
}
