package tepserver

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"mime/multipart"
	"net/http"

	"github.com/pingcap/log"
	"github.com/pingcap/tep/pkg/apis/apiserver/v1alpha1"
	"github.com/pingcap/tep/pkg/apis/tepserver/types"
	"github.com/pingcap/tep/pkg/operator"
	"github.com/pingcap/tep/pkg/utils"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func (s *Server) UploadMachineFile(files []*multipart.FileHeader) (map[string]interface{}, utils.APIStatus) {
	var machineList []v1alpha1.Machine
	for _, fileHeader := range files {
		machines, err := s.parseMachineList(fileHeader)
		if err != nil {
			return nil, utils.NewAPIStatus(http.StatusInternalServerError, err.Error())
		}
		if len(machines) < 1 {
			continue
		}
		machineList = append(machineList, machines...)
	}

	return s.uploadMachineList(machineList)
}

func (s *Server) GetMachineTemplate() ([]byte, utils.APIStatus) {
	c := s.newAPIServerClient("/machines-template")

	resp, err := c.Get()
	if err != nil {
		return nil, utils.NewAPIStatus(utils.ParseHTTPError(err), err.Error())
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, utils.NewAPIStatus(http.StatusInternalServerError, err.Error())
	}
	return body, utils.NewAPIStatus(resp.StatusCode, resp.Status)
}

// parseMachineList parse the records in CSV data and build a list of Machine
func (s *Server) parseMachineList(fileHeader *multipart.FileHeader) ([]v1alpha1.Machine, error) {
	reader, err := utils.ReadCSV(fileHeader)
	if err != nil {
		return nil, err
	}

	var machineList []v1alpha1.Machine
	records, err := reader.ReadAll()
	if err != nil {
		return nil, err
	}

	for i, record := range records {
		if i < 1 {
			// skip header
			continue
		}
		if len(record) != 5 {
			return nil, fmt.Errorf("format error, the csv records with incorrect column(s)")
		}
		machine := v1alpha1.Machine{
			ObjectMeta: metav1.ObjectMeta{
				Name: record[0],
			},
			NIC:     record[1],
			AZ:      record[2],
			Rack:    record[3],
			Purpose: record[4],
		}
		log.Debug("received machine data", zap.Any("machine", machine))
		machineList = append(machineList, machine)
	}

	return machineList, nil
}

func (s *Server) uploadMachineList(machineList []v1alpha1.Machine) (map[string]interface{}, utils.APIStatus) {
	c := utils.NewHTTPClient(s.getAPIServerFullURL("/importmachines"), s.TLSConfig)

	machineObj := &map[string]interface{}{
		"machines": machineList,
	}
	req, err := json.Marshal(machineObj)
	if err != nil {
		return nil, utils.NewAPIStatus(http.StatusInternalServerError, err.Error())
	}

	resp, err := c.Post(bytes.NewReader(req))
	if err != nil {
		return nil, utils.NewAPIStatus(utils.ParseHTTPError(err), err.Error())
	}
	defer resp.Body.Close()

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, utils.NewAPIStatus(http.StatusInternalServerError, err.Error())
	}

	var body map[string]interface{}
	err = json.Unmarshal(bodyBytes, &body)
	if err != nil {
		return nil, utils.NewAPIStatus(http.StatusInternalServerError, err.Error())
	}

	return body, utils.NewAPIStatus(resp.StatusCode, resp.Status)
}

// GetLableMachine list nodes via types
func (s *Server) GetLabelMachines() (*types.GetLabelMachinesResponse, utils.APIStatus) {
	response := &types.GetLabelMachinesResponse{
		Compute: 0,
		Storage: 0,
	}
	// build and send request to apiserver
	c := s.newAPIServerClient(fmt.Sprintf("/machines?purpose=%s&itemsPerPage=1", operator.TEPComputePurpose))
	resp, err := c.Get()
	if err != nil {
		return nil, utils.NewAPIStatus(utils.ParseHTTPError(err),
			fmt.Sprintf("failed to get machine list, err: %v", err.Error()))
	}
	defer resp.Body.Close()

	var machineList v1alpha1.MachineList
	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, utils.NewAPIStatus(http.StatusInternalServerError, fmt.Sprintf("unable to read response %v", resp))
	}

	err = json.Unmarshal(bytes, &machineList)
	if err != nil {
		log.Error("unable to decode response", zap.Any("data", resp))
		return nil, utils.NewAPIStatus(http.StatusInternalServerError, fmt.Sprintf("unable to decode response %v", resp))
	}
	response.Compute = machineList.Total

	s.SetURL(c, fmt.Sprintf("/machines?purpose=%s&itemsPerPage=1", operator.TEPStoragePurpose))
	resp, err = c.Get()
	if err != nil {
		return nil, utils.NewAPIStatus(utils.ParseHTTPError(err),
			fmt.Sprintf("failed to get machine list, err: %v", err.Error()))
	}
	defer resp.Body.Close()

	bytes, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, utils.NewAPIStatus(http.StatusInternalServerError, fmt.Sprintf("unable to read response %v", resp))
	}

	err = json.Unmarshal(bytes, &machineList)
	if err != nil {
		log.Error("unable to decode response", zap.Any("data", resp))
		return nil, utils.NewAPIStatus(http.StatusInternalServerError, fmt.Sprintf("unable to decode response %v", resp))
	}
	response.Storage = machineList.Total

	return response, utils.NewAPIStatus(resp.StatusCode, resp.Status)
}
