package oci

import (
	"encoding/base64"
	"strings"
	"testing"

	"github.com/oracle/oci-go-sdk/v65/common"
	"github.com/oracle/oci-go-sdk/v65/core"
)

func TestApplyVnicAddrs(t *testing.T) {
	primary := core.Vnic{
		IsPrimary: common.Bool(true),
		SubnetId:  common.String("sub-a"),
		PrivateIp: common.String("10.0.0.2"),
		PublicIp:  common.String("1.1.1.1"),
	}
	secondary := core.Vnic{
		IsPrimary: common.Bool(false),
		SubnetId:  common.String("sub-b"),
		PrivateIp: common.String("10.0.0.9"),
	}
	secondary2 := core.Vnic{
		SubnetId:  common.String("sub-c"),
		PrivateIp: common.String("10.0.0.7"),
	}
	tests := []struct {
		name        string
		order       []core.Vnic
		wantPrivate string
		wantSubnet  string
	}{
		{name: "主卡先到不被次卡覆盖", order: []core.Vnic{primary, secondary}, wantPrivate: "10.0.0.2", wantSubnet: "sub-a"},
		{name: "次卡先兜底主卡后覆盖", order: []core.Vnic{secondary, primary}, wantPrivate: "10.0.0.2", wantSubnet: "sub-a"},
		{name: "无主卡时先到者保留", order: []core.Vnic{secondary, secondary2}, wantPrivate: "10.0.0.9", wantSubnet: "sub-b"},
		{name: "仅次卡也能兜底", order: []core.Vnic{secondary2}, wantPrivate: "10.0.0.7", wantSubnet: "sub-c"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			inst := &Instance{}
			seen := make(map[*Instance]bool)
			for _, v := range tt.order {
				applyVnicAddrs(inst, v, seen)
			}
			if inst.PrivateIP != tt.wantPrivate || inst.SubnetID != tt.wantSubnet {
				t.Errorf("got (%q, %q), want (%q, %q)", inst.PrivateIP, inst.SubnetID, tt.wantPrivate, tt.wantSubnet)
			}
		})
	}
}

func TestShellSingleQuote(t *testing.T) {
	tests := []struct {
		name string
		in   string
		want string
	}{
		{name: "普通", in: "Passw0rd", want: "'Passw0rd'"},
		{name: "含单引号", in: "a'b", want: `'a'\''b'`},
		{name: "含空格特殊符", in: "p @ss!", want: "'p @ss!'"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := shellSingleQuote(tt.in); got != tt.want {
				t.Errorf("shellSingleQuote(%q) = %q, want %q", tt.in, got, tt.want)
			}
		})
	}
}

func TestRootPasswordUserData(t *testing.T) {
	encoded := rootPasswordUserData("Secret123")
	raw, err := base64.StdEncoding.DecodeString(encoded)
	if err != nil {
		t.Fatalf("result is not base64: %v", err)
	}
	script := string(raw)
	for _, want := range []string{
		"#!/bin/bash",
		"echo root:'Secret123' | chpasswd",
		"PermitRootLogin yes",
		"PasswordAuthentication yes",
		"systemctl restart sshd",
	} {
		if !strings.Contains(script, want) {
			t.Errorf("script missing %q\n---\n%s", want, script)
		}
	}
}

func TestBuildInstanceMetadataRootPasswordOverridesUserData(t *testing.T) {
	md := buildInstanceMetadata(CreateInstanceInput{
		RootPassword: "pw",
		UserData:     "ZXhwbGljaXQ=", // "explicit"
	})
	decoded, _ := base64.StdEncoding.DecodeString(md["user_data"])
	if !strings.Contains(string(decoded), "chpasswd") {
		t.Errorf("user_data = %q, want root password script (not explicit userData)", md["user_data"])
	}
}

func TestBuildInstanceSourceVpusPerGB(t *testing.T) {
	src := buildInstanceSource(CreateInstanceInput{ImageID: "img", BootVolumeSizeGBs: 100, BootVolumeVpusPerGB: 20})
	img, ok := src.(core.InstanceSourceViaImageDetails)
	if !ok {
		t.Fatalf("source type = %T, want image", src)
	}
	if img.BootVolumeVpusPerGB == nil || *img.BootVolumeVpusPerGB != 20 {
		t.Errorf("BootVolumeVpusPerGB = %v, want 20", img.BootVolumeVpusPerGB)
	}
}

func TestBuildInstanceSource(t *testing.T) {
	tests := []struct {
		name     string
		in       CreateInstanceInput
		wantType string
	}{
		{
			name:     "从镜像启动",
			in:       CreateInstanceInput{ImageID: "ocid1.image..a", BootVolumeSizeGBs: 100},
			wantType: "image",
		},
		{
			name:     "从引导卷启动",
			in:       CreateInstanceInput{BootVolumeID: "ocid1.bootvolume..b"},
			wantType: "bootVolume",
		},
		{
			name:     "引导卷优先于镜像",
			in:       CreateInstanceInput{ImageID: "ocid1.image..a", BootVolumeID: "ocid1.bootvolume..b"},
			wantType: "bootVolume",
		},
		{
			name:     "两者皆无返回 nil",
			in:       CreateInstanceInput{},
			wantType: "nil",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			src := buildInstanceSource(tt.in)
			switch tt.wantType {
			case "image":
				img, ok := src.(core.InstanceSourceViaImageDetails)
				if !ok {
					t.Fatalf("source type = %T, want InstanceSourceViaImageDetails", src)
				}
				if got, want := *img.BootVolumeSizeInGBs, tt.in.BootVolumeSizeGBs; got != want {
					t.Errorf("BootVolumeSizeInGBs = %d, want %d", got, want)
				}
			case "bootVolume":
				if _, ok := src.(core.InstanceSourceViaBootVolumeDetails); !ok {
					t.Fatalf("source type = %T, want InstanceSourceViaBootVolumeDetails", src)
				}
			case "nil":
				if src != nil {
					t.Fatalf("source = %v, want nil", src)
				}
			}
		})
	}
}

func TestBuildLaunchDetails(t *testing.T) {
	in := CreateInstanceInput{
		AvailabilityDomain: "AD-1",
		DisplayName:        "vm1",
		Shape:              "VM.Standard.A1.Flex",
		Ocpus:              2,
		MemoryInGBs:        12,
		ImageID:            "ocid1.image..a",
		SubnetID:           "ocid1.subnet..s",
		AssignPublicIP:     true,
		AssignIpv6:         true,
		SSHPublicKey:       "ssh-ed25519 AAA",
	}
	details, err := buildLaunchDetails("ocid1.tenancy..t", in)
	if err != nil {
		t.Fatalf("buildLaunchDetails: %v", err)
	}
	if got, want := *details.ShapeConfig.Ocpus, float32(2); got != want {
		t.Errorf("Ocpus = %v, want %v", got, want)
	}
	if got, want := *details.ShapeConfig.MemoryInGBs, float32(12); got != want {
		t.Errorf("MemoryInGBs = %v, want %v", got, want)
	}
	if got, want := *details.CreateVnicDetails.SubnetId, in.SubnetID; got != want {
		t.Errorf("SubnetId = %v, want %v", got, want)
	}
	if details.CreateVnicDetails.AssignIpv6Ip == nil || !*details.CreateVnicDetails.AssignIpv6Ip {
		t.Error("AssignIpv6Ip not set, want true")
	}
	if got, want := details.Metadata["ssh_authorized_keys"], in.SSHPublicKey; got != want {
		t.Errorf("ssh_authorized_keys = %q, want %q", got, want)
	}
}

func TestBuildLaunchDetailsRequiresSource(t *testing.T) {
	_, err := buildLaunchDetails("ocid1.tenancy..t", CreateInstanceInput{
		AvailabilityDomain: "AD-1", DisplayName: "vm1", Shape: "shape", SubnetID: "sub",
	})
	if err == nil {
		t.Error("buildLaunchDetails without source: got nil error, want failure")
	}
}

func TestBuildLaunchDetailsNoShapeConfig(t *testing.T) {
	details, err := buildLaunchDetails("t", CreateInstanceInput{
		AvailabilityDomain: "AD-1", DisplayName: "vm1", Shape: "VM.Standard.E2.1.Micro",
		ImageID: "img", SubnetID: "sub",
	})
	if err != nil {
		t.Fatalf("buildLaunchDetails: %v", err)
	}
	if details.ShapeConfig != nil {
		t.Errorf("ShapeConfig = %+v, want nil for fixed shape", details.ShapeConfig)
	}
	if details.Metadata != nil {
		t.Errorf("Metadata = %v, want nil when no key and user data", details.Metadata)
	}
}
