// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package baremetal

import (
	"encoding/json"
	"reflect"
	"testing"

	"github.com/stretchr/testify/assert"

	"yunion.io/x/jsonutils"
	"yunion.io/x/log"

	api "yunion.io/x/onecloud/pkg/apis/compute"
)

func TestParseDiskConfig(t *testing.T) {
	type args struct {
		desc string
	}

	var tAda int = 2
	var zAda int = 0
	var tStrip64k int64 = 64
	var splits40 string = "40%, "
	var splits100_32 string = "100g,32g,"

	tests := []struct {
		name    string
		args    args
		wantBdc api.BaremetalDiskConfig
		wantErr bool
	}{
		{
			name: "rotate:[1-2,4-5]:MegaRaid",
			args: args{"rotate:[1-2,4-5]:MegaRaid"},
			wantBdc: api.BaremetalDiskConfig{
				Type:   DISK_TYPE_ROTATE,
				Conf:   DISK_CONF_NONE,
				Driver: DISK_DRIVER_MEGARAID,
				Count:  0,
				Range:  []int64{1, 2, 4, 5},
			},
			wantErr: false,
		},
		{
			name: "rotate:[1-2,4,6]:raid10:marvelraid",
			args: args{"rotate:[1-2,4,6]:raid10:marvelraid"},
			wantBdc: api.BaremetalDiskConfig{
				Type:   DISK_TYPE_ROTATE,
				Conf:   DISK_CONF_RAID10,
				Driver: DISK_DRIVER_MARVELRAID,
				Count:  0,
				Range:  []int64{1, 2, 4, 6},
			},
			wantErr: false,
		},
		{
			name: "rotate:[4,6]:raid10",
			args: args{"rotate:[4,6]:raid10"},
			wantBdc: api.BaremetalDiskConfig{
				Type:  DISK_TYPE_ROTATE,
				Conf:  DISK_CONF_RAID10,
				Count: 0,
				Range: []int64{4, 6},
			},
			wantErr: false,
		},
		{
			name: "rotate:[4]:raid10:(40%, )",
			args: args{"rotate:[4]:raid10:(40%, )"},
			wantBdc: api.BaremetalDiskConfig{
				Type:   DISK_TYPE_ROTATE,
				Conf:   DISK_CONF_RAID10,
				Count:  0,
				Splits: splits40,
				Range:  []int64{4},
			},
			wantErr: false,
		},
		{
			name: "[12-13]:raid1:(100g,32g,):adapter0:strip64k",
			args: args{"[12-13]:raid1:(100g,32g,):adapter0:strip64k"},
			wantBdc: api.BaremetalDiskConfig{
				Type:    DISK_TYPE_HYBRID,
				Conf:    DISK_CONF_RAID1,
				Count:   0,
				Splits:  splits100_32,
				Adapter: &zAda,
				Strip:   &tStrip64k,
				Range:   []int64{12, 13},
			},
			wantErr: false,
		},
		{
			name: "6:raid5:adapter2",
			args: args{"6:raid5:adapter2"},
			wantBdc: api.BaremetalDiskConfig{
				Type:    DISK_TYPE_HYBRID,
				Conf:    DISK_CONF_RAID5,
				Count:   6,
				Adapter: &tAda,
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotBdc, err := ParseDiskConfig(tt.args.desc)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseDiskConfig() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotBdc, tt.wantBdc) {
				t.Errorf("ParseDiskConfig() = %v, want %v", gotBdc, tt.wantBdc)
			}
		})
	}
}

var (
	testStorages = []*BaremetalStorage{
		// 0-11 disk on adapter2
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    2861056,
			Adapter: 2,
		},

		// 0-1 disk on adapter0
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    953344,
			Adapter: 0,
		},
		{
			Driver:  DISK_DRIVER_MEGARAID,
			Rotate:  true,
			Size:    953344,
			Adapter: 0,
		},
	}
	bitmainStorage = []*BaremetalStorage{
		{
			Driver:       DISK_DRIVER_MARVELRAID,
			Rotate:       true,
			Size:         3814912,
			Adapter:      0,
			Enclosure:    252,
			MaxStripSize: 1024,
			MinStripSize: 64,
			Model:        "K7GTE9JL HGST HUS726040ALE610 APGNT907",
			Slot:         0,
			Status:       "online",
		},
		{
			Driver:       DISK_DRIVER_MARVELRAID,
			Rotate:       true,
			Size:         3814912,
			Adapter:      0,
			Enclosure:    252,
			MaxStripSize: 1024,
			MinStripSize: 64,
			Model:        "K7GTE9JL HGST HUS726040ALE610 APGNT907",
			Slot:         1,
			Status:       "online",
		},
		{
			Driver:       DISK_DRIVER_MARVELRAID,
			Rotate:       true,
			Size:         3814912,
			Adapter:      0,
			Enclosure:    252,
			MaxStripSize: 1024,
			MinStripSize: 64,
			Model:        "K7GTE9JL HGST HUS726040ALE610 APGNT907",
			Slot:         2,
			Status:       "online",
		},
		{
			Driver:       DISK_DRIVER_MARVELRAID,
			Rotate:       true,
			Size:         3814912,
			Adapter:      0,
			Enclosure:    252,
			MaxStripSize: 1024,
			MinStripSize: 64,
			Model:        "K7GTE9JL HGST HUS726040ALE610 APGNT907",
			Slot:         3,
			Status:       "online",
		},
		{
			Driver:       DISK_DRIVER_PCIE,
			Rotate:       true,
			Size:         2200000,
			Adapter:      0,
			Enclosure:    252,
			MaxStripSize: 1024,
			MinStripSize: 64,
			Model:        "NVME test",
			Slot:         4,
			Status:       "online",
		},
		{
			Driver:       DISK_DRIVER_PCIE,
			Rotate:       true,
			Size:         2200000,
			Adapter:      0,
			Enclosure:    252,
			MaxStripSize: 1024,
			MinStripSize: 64,
			Model:        "NVME test",
			Slot:         5,
			Status:       "online",
		},
	}
)

func TestCalculateLayout(t *testing.T) {
	confs, err := NewBaremetalDiskConfigs(
		"[0-1]:raid1:(100g,32g,):adapter0",
		"6:raid5:adapter2",
		"6:raid5:adapter2",
	)
	if err != nil {
		t.Fatalf("NewDiskConfigs err: %v", err)
	}

	expectedLayoutJson := `
[
  {
    "disks": [
      {
        "rotate": true,
        "driver": "MegaRaid",
        "size": 953344,
        "index": 0
      },
      {
        "rotate": true,
        "driver": "MegaRaid",
        "size": 953344,
        "index": 1
      }
    ],
    "conf": {
      "type": "hybrid",
      "conf": "raid1",
      "count": 0,
      "range": [
        0,
        1
      ],
      "splits": "100g,32g,",
      "size": [
        102400,
        32768,
        818176
      ],
      "adapter": 0,
	  "driver": ""
    },
    "size": 953344
  },
  {
    "disks": [
      {
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "rotate": true,
        "index": 0
      },
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 1
      },
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 2
      },
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 3
      },
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 4
      },
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 5
      }
    ],
    "conf": {
      "type": "hybrid",
      "conf": "raid5",
      "count": 6,
      "range": null,
      "splits": "",
      "size": null,
      "adapter": 2,
	  "driver": ""
    },
    "size": 14305280
  },
  {
    "disks": [
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 6
      },
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 7
      },
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 8
      },
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 9
      },
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 10
      },
      {
        "rotate": true,
        "adapter": 2,
        "driver": "MegaRaid",
        "size": 2861056,
        "index": 11
      }
    ],
    "conf": {
      "type": "hybrid",
      "conf": "raid5",
      "count": 6,
      "range": null,
      "splits": "",
      "size": null,
      "adapter": 2,
	  "driver": ""
    },
    "size": 14305280
  }
]
`
	layout, err := CalculateLayout(confs, testStorages)
	if err != nil {
		t.Fatalf("CalculateLayout err: %v", err)
	}

	var expectedLayout []Layout
	err = json.Unmarshal([]byte(expectedLayoutJson), &expectedLayout)
	if err != nil {
		t.Fatalf("Unmarshal expectedLayoutJson err: %v", err)
	}

	assert := assert.New(t)
	if !assert.Equal(expectedLayout, layout) {
		t.Errorf("CalculateLayout() = %v, want %v", layout, expectedLayout)
	}
}

func TestCalculateLayout2(t *testing.T) {
	adapater0 := 0
	confs := []*api.BaremetalDiskConfig{
		{
			Adapter: &adapater0,
			Conf:    DISK_CONF_RAID1,
			Count:   2,
			Driver:  DISK_DRIVER_MEGARAID,
			Range:   []int64{12, 13},
			Type:    DISK_TYPE_ROTATE,
		},
		{
			Adapter: &adapater0,
			Conf:    DISK_CONF_RAID10,
			Count:   12,
			Driver:  DISK_DRIVER_MEGARAID,
			Range:   []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
			Type:    DISK_TYPE_ROTATE,
		},
		{
			Adapter: &adapater0,
			Conf:    DISK_CONF_NONE,
			Count:   1,
			Driver:  DISK_DRIVER_PCIE,
			Range:   []int64{0},
			Type:    DISK_TYPE_SSD,
		},
	}
	storages := []*BaremetalStorage{}
	storageJson := `[
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1A1JPM",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 0,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1AMGGM",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 1,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1AN53M",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 2,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1ATVBM",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 3,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1AR4VM",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 4,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1ARYBM",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 5,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1AS36M",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 6,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1A1K6M",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 7,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1AM98M",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 8,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1ATTLM",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 9,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1AR3DM",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 10,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "HGST HUS728T8TAL5200 A9Y2VY1AN3DM",
        "rotate": true,
        "sector": 15626993664,
        "size": 7630368,
        "slot": 11,
        "status": "unconfigured_good"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "TOSHIBA AL15SEB060N 310253K0A07BFYVH",
        "rotate": true,
        "sector": 1171062784,
        "size": 571808,
        "slot": 12,
        "status": "online"
    },
    {
        "adapter": 0,
        "block": 512,
        "driver": "MegaRaid",
        "enclousure": 65,
        "index": 0,
        "max_strip_size": 0,
        "min_strip_size": 0,
        "model": "TOSHIBA AL15SEB060N 310253K0A0EDFYVH",
        "rotate": true,
        "sector": 1171062784,
        "size": 571808,
        "slot": 13,
        "status": "online"
    },
    {
        "adapter": 0,
        "block": 512,
        "dev": "nvme0n1",
        "driver": "",
        "enclousure": 0,
        "index": 0,
        "kernel": "unknown",
        "max_strip_size": 0,
        "min_strip_size": 0,
        "module": "INSPUR-NS8600G2U640",
        "pci_class": "unknown",
        "rotate": false,
        "sector": 12502446768,
        "size": 6104710,
        "slot": 0
    }
]`
	storageObj, err := jsonutils.ParseString(storageJson)
	if err != nil {
		t.Fatalf("parse json error: %v", err)
	}
	if err := storageObj.Unmarshal(&storages); err != nil {
		t.Fatalf("unmarshal json to storages: %v", err)
	}
	layout, err := CalculateLayout(confs, storages)
	if err != nil {
		t.Fatalf("calculate layout: %v", err)
	}
	log.Infof("layout: %+v", layout)
}

func TestCheckDisksAllocable(t *testing.T) {
	confs, err := NewBaremetalDiskConfigs(
		"[0-1]:raid1:(100g,32g,):adapter0",
		"6:raid5:adapter2",
		"6:raid5:adapter2",
	)
	bitmainConfs, err := NewBaremetalDiskConfigs("MarvelRaid:raid10:(60g,)")
	if err != nil {
		t.Fatalf("NewDiskConfigs err: %v", err)
	}

	layout, err := CalculateLayout(confs, testStorages)
	defaultLayout, err := CalculateLayout([]*api.BaremetalDiskConfig{&BaremetalDefaultDiskConfig}, testStorages[12:])
	if err != nil {
		t.Fatalf("CalculateLayout err: %v", err)
	}
	bitmainLayout, err := CalculateLayout(bitmainConfs, bitmainStorage)
	if err != nil {
		t.Fatalf("Calculate bitmain layout err: %v", err)
	}

	tdiskDefault := []*api.DiskConfig{
		{SizeMb: -1},
		{SizeMb: -1},
	}

	tdisk1 := []*api.DiskConfig{
		{SizeMb: 960000},
		{SizeMb: -1},
		{SizeMb: -1},
		{SizeMb: -1},
	}

	tdisk2 := []*api.DiskConfig{
		{SizeMb: -1},
		{SizeMb: -1},
		{SizeMb: -1},
		{SizeMb: -1},
	}

	tdisk3 := []*api.DiskConfig{
		{SizeMb: 102398},
		{SizeMb: -1},
		{SizeMb: -1},
		{SizeMb: -1},
	}

	btdisk1 := []*api.DiskConfig{
		{SizeMb: 61438},
		{SizeMb: -1},
	}

	btdisk2 := []*api.DiskConfig{
		{SizeMb: 61440},
		{SizeMb: -1},
	}

	btdisk3 := []*api.DiskConfig{
		{SizeMb: -1},
		{SizeMb: -1},
	}

	btPcieDisk := []*api.DiskConfig{
		{SizeMb: 44440},
		{SizeMb: -1},
		{SizeMb: -1},
		{SizeMb: -1},
		{SizeMb: -1},
	}

	type args struct {
		layouts []Layout
		disks   []*api.DiskConfig
	}
	tests := []struct {
		name       string
		args       args
		want       bool
		extraCount int
	}{
		{
			name: "default none type config should allocable",
			args: args{
				layouts: defaultLayout,
				disks:   tdiskDefault,
			},
			want:       true,
			extraCount: 0,
		},
		{
			name: "should not allocable",
			args: args{
				layouts: layout,
				disks:   tdisk1,
			},
			want:       false,
			extraCount: 0,
		},
		{
			name: "should allocable",
			args: args{
				layouts: layout,
				disks:   tdisk2,
			},
			want:       true,
			extraCount: 1,
		},
		{
			name: "should allocable2",
			args: args{
				layouts: layout,
				disks:   tdisk3,
			},
			want:       true,
			extraCount: 1,
		},
		{
			name: "Bitmain allocable 61438 should true",
			args: args{
				layouts: bitmainLayout,
				disks:   btdisk1,
			},
			want:       true,
			extraCount: 2,
		},
		{
			name: "Bitmain allocable 61440 should false",
			args: args{
				layouts: bitmainLayout,
				disks:   btdisk2,
			},
			want:       false,
			extraCount: 0,
		},
		{
			name: "Bitmain allocable autoextend should true",
			args: args{
				layouts: bitmainLayout,
				disks:   btdisk3,
			},
			want:       true,
			extraCount: 2,
		},
		{
			name: "Bitmain allocable PCIE disk should true",
			args: args{
				layouts: bitmainLayout,
				disks:   btPcieDisk,
			},
			want:       true,
			extraCount: 0,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got, extraDisks := CheckDisksAllocable(tt.args.layouts, tt.args.disks); got != tt.want || len(extraDisks) != tt.extraCount {
				t.Errorf("CheckDisksAllocable() = %v, %d, want %v %d", got, len(extraDisks), tt.want, tt.extraCount)
			}
		})
	}
}

var testStorages2 string = `
[
  {
    "adapter": 0,
    "driver": "MarvelRaid",
    "model": "SSDSCKJB120G7R",
    "rotate": true,
    "size": 114473,
    "slot": 0,
    "sn": "PHDW80440155150A"
  },
  {
    "adapter": 0,
    "driver": "MarvelRaid",
    "model": "SSDSCKJB120G7R",
    "rotate": true,
    "size": 114473,
    "slot": 1,
    "sn": "PHDW80440180150A"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "BTYM73040578960CGNSSDSC2KG960G7R SCV1DL56",
    "rotate": false,
    "size": 915200,
    "slot": 0,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "BTYM73040566960CGNSSDSC2KG960G7R SCV1DL56",
    "rotate": false,
    "size": 915200,
    "slot": 1,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "BTYM734403W7960CGNSSDSC2KG960G7R SCV1DL56",
    "rotate": false,
    "size": 915200,
    "slot": 2,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "BTYM734600B1960CGNSSDSC2KG960G7R SCV1DL56",
    "rotate": false,
    "size": 915200,
    "slot": 3,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A192FL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 4,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A1A4FL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 5,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A190FL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 6,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A15WFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 7,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A18CFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 8,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A1ABFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 9,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A1ACFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 10,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012840A23VFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 11,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012840A21SFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 12,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A191FL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 13,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A15UFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 14,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012840A23TFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 15,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A13GFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 16,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A13JFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 17,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A19EFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 18,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A19UFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 19,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A1BSFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 20,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A1B5FL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 21,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A16FFL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 22,
    "status": "offline"
  },
  {
    "adapter": 0,
    "driver": "MegaRaid",
    "enclosure": 64,
    "max_strip_size": 1024,
    "min_strip_size": 64,
    "model": "TOSHIBA AL14SEB18EQY EB012850A165FL7E",
    "rotate": true,
    "size": 1716352,
    "slot": 23,
    "status": "offline"
  }
]
`

func TestStorageLoad(t *testing.T) {
	ss := make([]*BaremetalStorage, 0)
	confs, err := NewBaremetalDiskConfigs(
		"2:raid1:MarvelRaid",
		"4:raid10:MegaRaid",
		"raid5",
	)
	if err != nil {
		t.Fatalf("NewDiskConfigs err: %v", err)
	}
	json.Unmarshal([]byte(testStorages2), &ss)
	_, err = CalculateLayout(confs, ss)
	if err != nil {
		t.Fatalf("CalculateLayout err: %v", err)
	}
}

func TestCalculateSize(t *testing.T) {
	type args struct {
		conf     string
		storages []*BaremetalStorage
	}
	tests := []struct {
		name string
		args args
		want int64
	}{
		{
			name: "NoneRaid",
			args: args{
				conf:     "",
				storages: testStorages,
			},
			want: 2861056*12 + 953344*2,
		},
		{
			name: "RAID1",
			args: args{
				conf:     DISK_CONF_RAID1,
				storages: testStorages,
			},
			want: 953344 * (12 + 2) / 2,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := CalculateSize(tt.args.conf, tt.args.storages); got != tt.want {
				t.Errorf("CalculateSize() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestGetDiskSpecs(t *testing.T) {
	tests := []struct {
		name     string
		storages []*BaremetalStorage
		want     []*api.DiskSpec
	}{
		{
			name:     "empty storage",
			storages: []*BaremetalStorage{},
			want:     []*api.DiskSpec{},
		},
		{
			name:     "normal input",
			storages: testStorages,
			want: []*api.DiskSpec{
				{
					Type:       HDD_DISK_SPEC_TYPE,
					Size:       2861056,
					StartIndex: 0,
					EndIndex:   11,
					Count:      12,
				},
				{
					Type:       HDD_DISK_SPEC_TYPE,
					Size:       953344,
					StartIndex: 12,
					EndIndex:   13,
					Count:      2,
				},
			},
		},
		{
			name: "discontinuity check",
			storages: []*BaremetalStorage{
				{
					Rotate: false,
					Size:   30,
				},
				{
					Rotate: true,
					Size:   20,
				},
				{
					Rotate: true,
					Size:   20,
				},
				{
					Rotate: false,
					Size:   30,
				},
			},
			want: []*api.DiskSpec{
				{
					Type:       getStorageDiskType(false),
					Size:       30,
					StartIndex: 0,
					EndIndex:   0,
					Count:      1,
				},
				{
					Type:       getStorageDiskType(true),
					Size:       20,
					StartIndex: 1,
					EndIndex:   2,
					Count:      2,
				},
				{
					Type:       getStorageDiskType(false),
					Size:       30,
					StartIndex: 3,
					EndIndex:   3,
					Count:      1,
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := GetDiskSpecs(tt.storages); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("GetDiskSpecs() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestValidateDiskConfigs(t *testing.T) {
	tests := []struct {
		name    string
		confs   []*api.BaremetalDiskConfig
		wantErr bool
	}{
		{
			name:    "empty",
			confs:   []*api.BaremetalDiskConfig{},
			wantErr: false,
		},
		{
			name: "none_raid",
			confs: []*api.BaremetalDiskConfig{
				{Conf: DISK_CONF_NONE},
				{Conf: DISK_CONF_RAID0},
			},
			wantErr: true,
		},
		{
			name: "raid_none_raid",
			confs: []*api.BaremetalDiskConfig{
				{Conf: DISK_CONF_RAID0},
				{Conf: DISK_CONF_NONE},
				{Conf: DISK_CONF_RAID5},
			},
			wantErr: true,
		},
		{
			name: "nones",
			confs: []*api.BaremetalDiskConfig{
				{Conf: DISK_CONF_NONE},
				{Conf: DISK_CONF_NONE},
			},
			wantErr: false,
		},
		{
			name: "raids_nones",
			confs: []*api.BaremetalDiskConfig{
				{Conf: DISK_CONF_RAID0},
				{Conf: DISK_CONF_RAID5},
				{Conf: DISK_CONF_NONE},
				{Conf: DISK_CONF_NONE},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := ValidateDiskConfigs(tt.confs); (err != nil) != tt.wantErr {
				t.Errorf("ValidateDiskConfigs() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestRetrieveStorages(t *testing.T) {
	type args struct {
		diskConfig *api.BaremetalDiskConfig
		storages   []*BaremetalStorage
	}
	s1 := &BaremetalStorage{
		Size:    1,
		Driver:  "MegaRaid",
		Adapter: 0,
	}
	s2 := &BaremetalStorage{
		Size:    1,
		Driver:  "MegaRaid",
		Adapter: 0,
	}
	s3 := &BaremetalStorage{
		Size:    2,
		Driver:  "MegaRaid",
		Adapter: 0,
	}
	tests := []struct {
		name         string
		args         args
		wantSelected []*BaremetalStorage
		wantRest     []*BaremetalStorage
	}{
		{
			name: "select all",
			args: args{
				diskConfig: &api.BaremetalDiskConfig{
					Driver: "MegaRaid",
					Count:  0,
					Type:   api.DISK_TYPE_HYBRID,
				},
				storages: []*BaremetalStorage{s1, s2, s3},
			},
			wantSelected: []*BaremetalStorage{s1, s2, s3},
			wantRest:     []*BaremetalStorage{},
		},
		{
			name: "select count 2",
			args: args{
				diskConfig: &api.BaremetalDiskConfig{
					Driver: "MegaRaid",
					Count:  2,
					Type:   api.DISK_TYPE_HYBRID,
				},
				storages: []*BaremetalStorage{s1, s2, s3},
			},
			wantSelected: []*BaremetalStorage{s1, s2},
			wantRest:     []*BaremetalStorage{s3},
		},
		{
			name: "select by range",
			args: args{
				diskConfig: &api.BaremetalDiskConfig{
					Driver: "MegaRaid",
					Range:  []int64{1, 2},
					Type:   api.DISK_TYPE_HYBRID,
				},
				storages: []*BaremetalStorage{s1, s2, s3},
			},
			wantSelected: []*BaremetalStorage{s2, s3},
			wantRest:     []*BaremetalStorage{s1},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotSelected, gotRest, err := RetrieveStorages(tt.args.diskConfig, tt.args.storages)
			if !reflect.DeepEqual(gotSelected, tt.wantSelected) {
				t.Errorf("RetrieveStorages() gotSelected = %v, want %v, err: %s", gotSelected, tt.wantSelected, err)
			}
			if !reflect.DeepEqual(gotRest, tt.wantRest) {
				t.Errorf("RetrieveStorages() gotRest = %v, want %v, err: %s", gotRest, tt.wantRest, err)
			}
		})
	}
}

func TestGetSplitSizes(t *testing.T) {
	type args struct {
		size      int64
		splitConf string
	}
	var totalSize int64 = 22889472
	size10p := int64(float64(totalSize) * 0.1)
	tests := []struct {
		name string
		args args
		want []int64
	}{
		{
			name: "split gb",
			args: args{
				size:      totalSize,
				splitConf: "60g,",
			},
			want: []int64{61440, totalSize - 61440},
		},
		{
			name: "split by percent",
			args: args{
				size:      totalSize,
				splitConf: "10%,",
			},
			want: []int64{
				size10p,
				totalSize - size10p,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := GetSplitSizes(tt.args.size, tt.args.splitConf); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("GetSplitSizes() = %v, want %v", got, tt.want)
			}
		})
	}
}

var (
	pcieStorages = []*BaremetalStorage{
		{
			Driver:  DISK_DRIVER_LINUX,
			Rotate:  true,
			Size:    51200,
			Adapter: 0,
			Index:   0,
		},
		{
			Driver:  DISK_DRIVER_PCIE,
			Rotate:  false,
			Size:    61440,
			Adapter: 0,
			Index:   0,
		},
	}

	pcie2Storages = []*BaremetalStorage{
		{
			Driver:  DISK_DRIVER_LINUX,
			Dev:     "sda",
			Rotate:  true,
			Size:    3815447,
			Adapter: 0,
			Index:   0,
		},
		{
			Driver:  DISK_DRIVER_PCIE,
			Dev:     "nvme0n1",
			Rotate:  false,
			Size:    1953514,
			Adapter: 0,
			Index:   0,
		},
		{
			Driver:  DISK_DRIVER_PCIE,
			Dev:     "nvme1n1",
			Rotate:  false,
			Size:    122104,
			Adapter: 0,
			Index:   0,
		},
	}
)

func TestPCIEStoragesAllocable(t *testing.T) {
	adapter0 := 0
	confs := []*api.BaremetalDiskConfig{
		{
			Adapter: &adapter0,
			Conf:    DISK_CONF_NONE,
			Count:   1,
			Driver:  DISK_DRIVER_PCIE,
			Range:   []int64{0},
			Type:    DISK_TYPE_SSD,
		},
		{
			Adapter: &adapter0,
			Conf:    DISK_CONF_NONE,
			Count:   1,
			Driver:  DISK_DRIVER_LINUX,
			Range:   []int64{0},
			Type:    DISK_TYPE_ROTATE,
		},
	}

	layouts, err := CalculateLayout(confs, pcieStorages)
	if err != nil {
		t.Errorf("CalculateLayout error: %v", err)
		return
	}
	// log.Errorf("layouts: %s", jsonutils.Marshal(layouts))
	disks := []*api.DiskConfig{
		{
			Backend: api.STORAGE_LOCAL,
			Driver:  "scsi",
			SizeMb:  30720,
		},
		{
			Backend:    api.STORAGE_LOCAL,
			Driver:     "scsi",
			Fs:         "ext4",
			Mountpoint: "/opt",
			SizeMb:     -1,
		},
		{
			Backend:    api.STORAGE_LOCAL,
			Driver:     "scsi",
			Fs:         "ext4",
			Mountpoint: "/data",
			SizeMb:     -1,
		},
	}
	if ok := IsDisksAllocable(layouts, disks); !ok {
		t.Errorf("Disk not allocable")
	}
}

func Test2PCIEStoragesAllocable(t *testing.T) {
	adapter0 := 0
	confs := []*api.BaremetalDiskConfig{
		{
			Adapter: &adapter0,
			Conf:    DISK_CONF_NONE,
			Count:   0,
			Driver:  DISK_DRIVER_PCIE,
			Range:   []int64{1},
			Type:    DISK_TYPE_SSD,
		},
	}

	layouts, err := CalculateLayout(confs, pcie2Storages)
	if err != nil {
		t.Errorf("CalculateLayout error: %v", err)
		return
	}
	log.Errorf("layouts: %s", jsonutils.Marshal(layouts))
	disks := []*api.DiskConfig{
		{
			Backend: api.STORAGE_LOCAL,
			Driver:  "scsi",
			SizeMb:  -1,
		},
	}
	if ok := IsDisksAllocable(layouts, disks); !ok {
		t.Errorf("Disk not allocable")
	}
}
