/*
Copyright (c) 2026 Huawei Technologies Co., Ltd.
openFuyao is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
         http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
*/

package remote

import (
	"crypto/tls"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"gitcode.com/openFuyao/ub-ssu-csi/pkg/backend"
)

const (
	testSize1GiB        = 1 << 30
	testSize2GiB        = 2 << 30
	testSize4GiB        = 4 << 30
	testNamespaceCount2 = 2
	testNamespaceCount4 = 4
	truncateBodyMaxLen  = 256
	ellipsisLen         = 3 // length of "..." suffix in truncated strings
)

func setupTestClient(handler http.HandlerFunc) (*httptest.Server, *remoteClient) {
	ts := httptest.NewTLSServer(handler)
	httpClient := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true, // Skip TLS verification for test
			},
		},
	}
	ubseClient := NewHTTPClientBuilder(ts.URL).WithHTTPClient(httpClient).Build()
	client := &remoteClient{httpClient: ubseClient}
	return ts, client
}

func mockAllocSingleNSHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusCreated)
	resp := ubseResponse[allocResponseData]{
		Code: 0,
		Msg:  "success",
		Data: allocResponseData{
			Name:     "vol-1",
			Strategy: 2,
			NameSpaceList: []allocNamespace{
				{
					NsUuid:      "uuid-123",
					NsDevPath:   "/dev/nvme0n1",
					TgtEid:      "eid-1",
					TgtNqn:      "nqn-1",
					NsSize:      testSize2GiB,
					LbaFormat:   512,
					NamespaceId: 1,
				},
			},
		},
	}
	if err := json.NewEncoder(w).Encode(resp); err != nil {
		panic(err) // panic in test is acceptable
	}
}

func mockAllocMultiNSHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusCreated)
	resp := ubseResponse[allocResponseData]{
		Code: 0,
		Data: allocResponseData{
			Name:     "vol-2",
			Strategy: 0,
			NameSpaceList: []allocNamespace{
				{NsUuid: "uuid-1", NsDevPath: "/dev/nvme0n1", NsSize: testSize1GiB},
				{NsUuid: "uuid-2", NsDevPath: "/dev/nvme1n1", NsSize: testSize1GiB},
				{NsUuid: "uuid-3", NsDevPath: "/dev/nvme2n1", NsSize: testSize1GiB},
				{NsUuid: "uuid-4", NsDevPath: "/dev/nvme3n1", NsSize: testSize1GiB},
			},
		},
	}
	if err := json.NewEncoder(w).Encode(resp); err != nil {
		panic(err) // panic in test is acceptable
	}
}

func mockAllocHTTPErrorHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusInternalServerError)
	// Write error is ignored in test mock handlers as httptest.ResponseRecorder never fails
	if _, err := w.Write([]byte("internal error")); err != nil {
		panic(err) // panic in test is acceptable
	}
}

func mockAllocBusinessErrorHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusCreated)
	resp := ubseResponse[allocResponseData]{
		Code: 1,
		Msg:  "volume already exists",
	}
	if err := json.NewEncoder(w).Encode(resp); err != nil {
		panic(err) // panic in test is acceptable
	}
}

func mockDeleteSuccessHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	resp := ubseResponse[emptyData]{
		Code: 0,
		Msg:  "success",
	}
	if err := json.NewEncoder(w).Encode(resp); err != nil {
		panic(err) // panic in test is acceptable
	}
}

func mockDeleteHTTPErrorHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusInternalServerError)
	// Write error is ignored in test mock handlers as httptest.ResponseRecorder never fails
	if _, err := w.Write([]byte("internal error")); err != nil {
		panic(err) // panic in test is acceptable
	}
}

func TestAllocVolume(t *testing.T) {
	t.Run("成功场景", func(t *testing.T) {
		testAllocVolumeSuccess(t)
	})

	t.Run("失败场景", func(t *testing.T) {
		testAllocVolumeFailure(t)
	})
}

func testAllocVolumeSuccess(t *testing.T) {
	t.Run("单namespace分配成功", testAllocVolumeSingleNS)
	t.Run("多namespace分配成功", testAllocVolumeMultiNS)
	t.Run("LBA格式4096转换成功", testAllocVolumeLBAFormat)
}

func testAllocVolumeSingleNS(t *testing.T) {
	ts, client := setupTestClient(mockAllocSingleNSHandler)
	defer ts.Close()

	vol, err := client.AllocVolume("vol-1", testSize2GiB, backend.LogicalConfig{
		NsNum:     1,
		LbaFormat: 0,
		Strategy:  2,
	})
	require.NoError(t, err)
	require.NotNil(t, vol)
	assert.Equal(t, "vol-1", vol.VolumeID)
	assert.Equal(t, uint64(testSize2GiB), vol.SizeBytes)
	assert.Len(t, vol.Namespaces, 1)
	assert.Equal(t, "uuid-123", vol.Namespaces[0].DeviceUUID)
}

func testAllocVolumeMultiNS(t *testing.T) {
	ts, client := setupTestClient(mockAllocMultiNSHandler)
	defer ts.Close()
	vol, err := client.AllocVolume("vol-2", testSize4GiB, backend.LogicalConfig{
		NsNum:     4,
		LbaFormat: 0,
		Strategy:  0,
	})
	require.NoError(t, err)
	assert.Len(t, vol.Namespaces, testNamespaceCount4)
	assert.Equal(t, uint64(testSize4GiB), vol.SizeBytes)
}

func testAllocVolumeLBAFormat(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) {
		var req allocRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			t.Logf("decode error in test handler: %v", err)
		}
		assert.Equal(t, uint32(4096), req.LbaFormat)
		w.WriteHeader(http.StatusCreated)
		resp := ubseResponse[allocResponseData]{
			Code: 0,
			Data: allocResponseData{
				Name:          "vol-3",
				NameSpaceList: []allocNamespace{{NsUuid: "uuid-1", LbaFormat: 4096}},
			},
		}
		if err := json.NewEncoder(w).Encode(resp); err != nil {
			panic(err) // panic in test is acceptable
		}
	}
	ts, client := setupTestClient(handler)
	defer ts.Close()
	vol, err := client.AllocVolume("vol-3", testSize1GiB, backend.LogicalConfig{
		NsNum:     1,
		LbaFormat: 1,
		Strategy:  2,
	})
	require.NoError(t, err)
	assert.NotNil(t, vol)
}

func testAllocVolumeFailure(t *testing.T) {
	t.Run("HTTP错误", func(t *testing.T) {
		ts, client := setupTestClient(mockAllocHTTPErrorHandler)
		defer ts.Close()
		_, err := client.AllocVolume("vol-1", testSize1GiB, backend.LogicalConfig{})
		require.Error(t, err)
		assert.Contains(t, err.Error(), "alloc request")
	})

	t.Run("UBSE业务错误", func(t *testing.T) {
		ts, client := setupTestClient(mockAllocBusinessErrorHandler)
		defer ts.Close()
		_, err := client.AllocVolume("vol-1", testSize1GiB, backend.LogicalConfig{})
		require.Error(t, err)
		assert.Contains(t, err.Error(), "alloc request")
	})
}

func TestDeleteVolume(t *testing.T) {
	t.Run("成功场景", func(t *testing.T) {
		t.Run("删除成功", func(t *testing.T) {
			ts, client := setupTestClient(mockDeleteSuccessHandler)
			defer ts.Close()

			err := client.DeleteVolume("vol-1")
			require.NoError(t, err)
		})
		t.Run("幂等性验证", func(t *testing.T) {
			ts, client := setupTestClient(mockDeleteSuccessHandler)
			defer ts.Close()
			err1 := client.DeleteVolume("vol-1")
			require.NoError(t, err1)
			err2 := client.DeleteVolume("vol-1")
			require.NoError(t, err2)
		})
	})

	t.Run("失败场景", func(t *testing.T) {
		t.Run("HTTP错误", func(t *testing.T) {
			ts, client := setupTestClient(mockDeleteHTTPErrorHandler)
			defer ts.Close()
			err := client.DeleteVolume("vol-1")
			require.Error(t, err)
			assert.Contains(t, err.Error(), "delete request")
		})
	})
}

func TestLbaFormatConversion(t *testing.T) {
	t.Run("格式0转换为512字节", func(t *testing.T) {
		assert.Equal(t, uint32(512), lbaFormatToBytes(0))
	})
	t.Run("格式1转换为4096字节", func(t *testing.T) {
		assert.Equal(t, uint32(4096), lbaFormatToBytes(1))
	})
}

func TestTruncateBody(t *testing.T) {
	t.Run("短内容不截断", func(t *testing.T) {
		body := []byte("short body")
		result := truncateBody(body)
		assert.Equal(t, "short body", result)
	})
	t.Run("长内容截断并添加省略号", func(t *testing.T) {
		longBody := make([]byte, 300)
		for i := range longBody {
			longBody[i] = 'a'
		}
		result := truncateBody(longBody)
		assert.Len(t, result, truncateBodyMaxLen+ellipsisLen)
		assert.Contains(t, result, "...")
	})
}

func TestUnsupportedMethods(t *testing.T) {
	client := &remoteClient{}
	t.Run("GetVolumeStatus不支持", func(t *testing.T) {
		_, err := client.GetVolumeStatus("vol-1")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "not supported")
	})
	t.Run("GetVolumeInfo返回错误", func(t *testing.T) {
		_, err := client.GetVolumeInfo("vol-1")
		require.Error(t, err)
		assert.Equal(t, backend.ErrVolumeInfoNotFound, err)
	})
	t.Run("SetAccessPermission不支持", func(t *testing.T) {
		err := client.SetAccessPermission("vol-1", "host-nqn")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "not supported")
	})
	t.Run("RevokeAccessPermission不支持", func(t *testing.T) {
		err := client.RevokeAccessPermission("vol-1", "host-nqn")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "not supported")
	})
	t.Run("GetConnectionInfo返回nil", func(t *testing.T) {
		connInfo, err := client.GetConnectionInfo("vol-1")
		require.NoError(t, err)
		assert.Nil(t, connInfo)
	})
}