package oci

import (
	"context"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"
)

// TestRealOCIInstanceLifecycle 走真实实例完整生命周期：
// 建网络 → 查 AD/镜像 → 创建 A1 实例 → 等待 RUNNING → 校验 IP 补全 →
// 改名 → 引导卷查改 → STOP → 终止（不保留卷）→ 清理网络。
// A1 容量不足时跳过（试用区域常见）。
func TestRealOCIInstanceLifecycle(t *testing.T) {
	if os.Getenv("OCI_INTEGRATION_TEST") != "1" {
		t.Skip("set OCI_INTEGRATION_TEST=1 to run real OCI tests")
	}
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
	defer cancel()
	cred := loadTestCredentials(t, "试用期")
	client := NewClient()
	prefix := fmt.Sprintf("oci-portal-e2e-%d", time.Now().Unix())

	vcn, err := client.CreateVCN(ctx, cred, CreateVCNInput{
		DisplayName: prefix + "-vcn", CidrBlock: "10.98.0.0/16", DnsLabel: "e2einst",
	})
	if err != nil {
		t.Fatalf("CreateVCN: %v", err)
	}
	defer cleanupVCN(t, client, cred, vcn.ID)
	subnet, err := client.CreateSubnet(ctx, cred, CreateSubnetInput{
		VcnID: vcn.ID, DisplayName: prefix + "-subnet", CidrBlock: "10.98.1.0/24", DnsLabel: "e2esub",
	})
	if err != nil {
		t.Fatalf("CreateSubnet: %v", err)
	}

	ads, err := client.ListAvailabilityDomains(ctx, cred, "")
	if err != nil || len(ads) == 0 {
		t.Fatalf("ListAvailabilityDomains: %v (got %d)", err, len(ads))
	}
	images, err := client.ListImages(ctx, cred, ImagesQuery{
		OperatingSystem: "Canonical Ubuntu", Shape: "VM.Standard.A1.Flex",
	})
	if err != nil || len(images) == 0 {
		t.Fatalf("ListImages: %v (got %d)", err, len(images))
	}
	t.Logf("using ad=%s image=%s", ads[0], images[0].DisplayName)

	instance, err := client.LaunchInstance(ctx, cred, CreateInstanceInput{
		AvailabilityDomain: ads[0],
		DisplayName:        prefix + "-vm",
		Shape:              "VM.Standard.A1.Flex",
		Ocpus:              1,
		MemoryInGBs:        6,
		ImageID:            images[0].ID,
		BootVolumeSizeGBs:  50,
		SubnetID:           subnet.ID,
		AssignPublicIP:     true,
		SSHPublicKey:       "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEfake+e2e+key oci-portal-e2e",
	})
	if err != nil {
		if strings.Contains(err.Error(), "Out of host capacity") || strings.Contains(err.Error(), "OutOfCapacity") {
			t.Skipf("A1 capacity unavailable in this region: %v", err)
		}
		t.Fatalf("LaunchInstance: %v", err)
	}
	t.Logf("launched instance %s (%s)", instance.DisplayName, instance.ID)
	defer terminateAndWait(t, client, cred, instance.ID)

	if err := waitInstanceState(ctx, client, cred, instance.ID, "RUNNING"); err != nil {
		t.Fatalf("wait RUNNING: %v", err)
	}
	got, err := client.GetInstance(ctx, cred, "", instance.ID)
	if err != nil {
		t.Fatalf("GetInstance: %v", err)
	}
	t.Logf("instance running: shape=%s %gocpu/%gGB privateIp=%s publicIp=%s",
		got.Shape, got.Ocpus, got.MemoryInGBs, got.PrivateIP, got.PublicIP)
	if got.PrivateIP == "" {
		t.Error("PrivateIP empty, want enriched from VNIC")
	}
	if got.PublicIP == "" {
		t.Error("PublicIP empty, want assigned")
	}
	if got.SubnetID != subnet.ID {
		t.Errorf("SubnetID = %s, want %s", got.SubnetID, subnet.ID)
	}

	if _, err := client.UpdateInstance(ctx, cred, instance.ID, UpdateInstanceInput{DisplayName: prefix + "-vm-renamed"}); err != nil {
		t.Errorf("UpdateInstance: %v", err)
	}

	verifyBootVolume(ctx, t, client, cred, ads[0], instance.ID, prefix)

	if _, err := client.InstanceAction(ctx, cred, "", instance.ID, "STOP"); err != nil {
		t.Fatalf("InstanceAction STOP: %v", err)
	}
	if err := waitInstanceState(ctx, client, cred, instance.ID, "STOPPED"); err != nil {
		t.Fatalf("wait STOPPED: %v", err)
	}
	t.Log("instance stopped, terminating")
}

func verifyBootVolume(ctx context.Context, t *testing.T, client *RealClient, cred Credentials, ad, instanceID, prefix string) {
	t.Helper()
	volumes, err := client.ListBootVolumes(ctx, cred, "", ad)
	if err != nil {
		t.Errorf("ListBootVolumes: %v", err)
		return
	}
	for _, v := range volumes {
		detail, err := client.GetBootVolume(ctx, cred, "", v.ID)
		if err != nil {
			t.Errorf("GetBootVolume: %v", err)
			continue
		}
		if detail.AttachedInstanceID != instanceID {
			continue
		}
		t.Logf("boot volume %s size=%dGB vpus=%d attached=%s",
			detail.DisplayName, detail.SizeInGBs, detail.VpusPerGB, detail.AttachedInstanceID)
		if detail.SizeInGBs != 50 {
			t.Errorf("boot volume size = %d, want 50", detail.SizeInGBs)
		}
		if _, err := client.UpdateBootVolume(ctx, cred, v.ID, UpdateBootVolumeInput{DisplayName: prefix + "-bv"}); err != nil {
			t.Errorf("UpdateBootVolume: %v", err)
		}
		return
	}
	t.Errorf("no boot volume attached to instance %s found in %s", instanceID, ad)
}

// waitInstanceState 轮询实例直到到达目标状态。
func waitInstanceState(ctx context.Context, client *RealClient, cred Credentials, instanceID, want string) error {
	for i := 0; i < 60; i++ {
		inst, err := client.GetInstance(ctx, cred, "", instanceID)
		if err != nil {
			return fmt.Errorf("poll instance: %w", err)
		}
		if inst.LifecycleState == want {
			return nil
		}
		select {
		case <-ctx.Done():
			return fmt.Errorf("poll instance state %s: %w", want, ctx.Err())
		case <-time.After(5 * time.Second):
		}
	}
	return fmt.Errorf("poll instance state %s: timed out", want)
}

// terminateAndWait 终止实例（不保留引导卷）并等待 TERMINATED，
// 保证后续网络清理不被占用的 VNIC 阻塞。
func terminateAndWait(t *testing.T, client *RealClient, cred Credentials, instanceID string) {
	t.Helper()
	ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute)
	defer cancel()
	inst, err := client.GetInstance(ctx, cred, "", instanceID)
	if err == nil && inst.LifecycleState == "TERMINATED" {
		return
	}
	if err := client.TerminateInstance(ctx, cred, "", instanceID, false); err != nil {
		t.Errorf("cleanup: terminate instance: %v", err)
		return
	}
	if err := waitInstanceState(ctx, client, cred, instanceID, "TERMINATED"); err != nil {
		t.Errorf("cleanup: wait TERMINATED: %v (manual cleanup may be required)", err)
		return
	}
	t.Logf("cleanup: instance %s terminated", instanceID)
}
