// 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 outputs

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"testing"

	"github.com/evertras/bubble-table/table"

	"github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
	h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
)

const (
	mockFilesPath = "../../../test/static"
)

// helpers

// getInstanceTypeDetails unmarshalls the json file in the given testing folder
// and returns a list of instance type details.
func getInstanceTypeDetails(t *testing.T, file string) []*instancetypes.Details {
	folder := "FilterVerbose"
	mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, folder, file)
	mockFile, err := os.ReadFile(mockFilename)
	h.Assert(t, err == nil, "Error reading mock file "+mockFilename)

	instanceTypes := []*instancetypes.Details{}
	err = json.Unmarshal(mockFile, &instanceTypes)
	h.Assert(t, err == nil, fmt.Sprintf("Error parsing mock json file contents %s. Error: %v", mockFilename, err))
	return instanceTypes
}

// getRowsInstances reformats the given table rows into a list of instance type names.
func getRowsInstances(rows []table.Row) string {
	instances := []string{}

	for _, row := range rows {
		instances = append(instances, fmt.Sprintf("%v", row.Data["Instance Type"]))
	}

	return strings.Join(instances, ", ")
}

// tests

func TestNewBubbleTeaModel_Hypervisor(t *testing.T) {
	instanceTypes := getInstanceTypeDetails(t, "g3_16xlarge.json")

	// test non nil Hypervisor
	model := NewBubbleTeaModel(instanceTypes)
	rows := model.tableModel.table.GetVisibleRows()
	expectedHypervisor := "xen"
	actualHypervisor := rows[0].Data["Hypervisor"]

	h.Assert(t, actualHypervisor == expectedHypervisor, fmt.Sprintf("Hypervisor should be %s but instead is %s", expectedHypervisor, actualHypervisor))
}

func TestNewBubbleTeaModel_CPUArchitectures(t *testing.T) {
	instanceTypes := getInstanceTypeDetails(t, "g3_16xlarge.json")
	model := NewBubbleTeaModel(instanceTypes)
	rows := model.tableModel.table.GetVisibleRows()

	actualGPUArchitectures := "x86_64"
	expectedGPUArchitectures := rows[0].Data["CPU Arch"]

	h.Assert(t, actualGPUArchitectures == expectedGPUArchitectures, "CPU architecture should be (%s), but actually (%s)", expectedGPUArchitectures, actualGPUArchitectures)
}

func TestNewBubbleTeaModel_GPU(t *testing.T) {
	instanceTypes := getInstanceTypeDetails(t, "g3_16xlarge.json")
	model := NewBubbleTeaModel(instanceTypes)
	rows := model.tableModel.table.GetVisibleRows()

	// test GPU count
	expectedGPUCount := "4"
	actualGPUCount := fmt.Sprintf("%v", rows[0].Data["GPUs"])

	h.Assert(t, expectedGPUCount == actualGPUCount, "GPU count should be %s, but is actually %s", expectedGPUCount, actualGPUCount)

	// test GPU memory
	expectedGPUMemory := "32"
	actualGPUMemory := rows[0].Data["GPU Mem (GiB)"]

	h.Assert(t, expectedGPUMemory == actualGPUMemory, "GPU memory should be %s, but is actually %s", expectedGPUMemory, actualGPUMemory)

	// test GPU info
	expectedGPUInfo := "NVIDIA M60"
	actualGPUInfo := rows[0].Data["GPU Info"]

	h.Assert(t, expectedGPUInfo == actualGPUInfo, "GPU info should be (%s), but is actually (%s)", expectedGPUInfo, actualGPUInfo)
}

func TestNewBubbleTeaModel_ODPricing(t *testing.T) {
	instanceTypes := getInstanceTypeDetails(t, "g3_16xlarge.json")

	// test non nil OD price
	model := NewBubbleTeaModel(instanceTypes)
	rows := model.tableModel.table.GetVisibleRows()
	expectedODPrice := "$4.56"
	actualODPrice := fmt.Sprintf("%v", rows[0].Data["On-Demand Price/Hr"])

	h.Assert(t, actualODPrice == expectedODPrice, "Actual OD price should be %s, but is actually %s", expectedODPrice, actualODPrice)

	// test nil OD price
	instanceTypes[0].OndemandPricePerHour = nil
	model = NewBubbleTeaModel(instanceTypes)
	rows = model.tableModel.table.GetVisibleRows()
	expectedODPrice = "-Not Fetched-"
	actualODPrice = fmt.Sprintf("%v", rows[0].Data["On-Demand Price/Hr"])

	h.Assert(t, actualODPrice == expectedODPrice, "Actual OD price should be %s, but is actually %s", expectedODPrice, actualODPrice)
}

func TestNewBubbleTeaModel_SpotPricing(t *testing.T) {
	instanceTypes := getInstanceTypeDetails(t, "g3_16xlarge.json")

	// test non nil spot price
	model := NewBubbleTeaModel(instanceTypes)
	rows := model.tableModel.table.GetVisibleRows()
	expectedODPrice := "$1.368"
	actualODPrice := fmt.Sprintf("%v", rows[0].Data["Spot Price/Hr"])

	h.Assert(t, actualODPrice == expectedODPrice, "Actual spot price should be %s, but is actually %s", expectedODPrice, actualODPrice)

	// test nil spot price
	instanceTypes[0].SpotPrice = nil
	model = NewBubbleTeaModel(instanceTypes)
	rows = model.tableModel.table.GetVisibleRows()
	expectedODPrice = "-Not Fetched-"
	actualODPrice = fmt.Sprintf("%v", rows[0].Data["Spot Price/Hr"])

	h.Assert(t, actualODPrice == expectedODPrice, "Actual spot price should be %s, but is actually %s", expectedODPrice, actualODPrice)
}

func TestNewBubbleTeaModel_Rows(t *testing.T) {
	instanceTypes := getInstanceTypeDetails(t, "3_instances.json")
	model := NewBubbleTeaModel(instanceTypes)
	rows := model.tableModel.table.GetVisibleRows()

	h.Assert(t, len(rows) == len(instanceTypes), "Number of rows should be %d, but is actually %d", len(instanceTypes), len(rows))

	// test that order of instance types is retained
	for i := range instanceTypes {
		currInstanceName := instanceTypes[i].InstanceType
		currRowName := rows[i].Data["Instance Type"]

		h.Assert(t, string(currInstanceName) == currRowName, "Rows should be in following order: %s. Actual order: [%s]", OneLineOutput(instanceTypes), getRowsInstances(rows))
	}
}
