package internalrpc

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

	"github.com/labstack/echo/v4"

	"github.com/pingcap/test-infra/services/tcms/utils"

	"github.com/pingcap/test-infra/common/errors"
	"github.com/pingcap/test-infra/common/model/rms/request"
	"github.com/pingcap/test-infra/common/model/rms/response"
	"github.com/pingcap/test-infra/services/tcms/service"
	serror "github.com/pingcap/test-infra/services/tcms/service/common/errors"
)

type rmsRPCService struct {
}

func NewRMSRPCService() service.RMSRPCService {
	return &rmsRPCService{}
}

func (r *rmsRPCService) Ping(endpoint string) error {
	req, _ := http.NewRequest("GET", endpoint+"/ping", nil)

	resp, err := Client.Do(req)
	if err != nil {
		return serror.New(http.StatusInternalServerError, err)
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		return nil
	default:
		return r.handleStatusError(resp, endpoint)
	}
}

func (r *rmsRPCService) GetResourcePoolStatus(endpoint, token, resourcePool string) (*response.GetResourcePoolStatusResponse, *serror.Error) {
	req, _ := http.NewRequest(http.MethodGet, endpoint+"/api/v1/resource-pool/status", bytes.NewReader(
		utils.ToJSON(request.GetResourcePoolStatusRequest{
			ResourcePool: resourcePool,
		})))
	req.Header.Add(echo.HeaderAuthorization, token)
	req.Header.Add(echo.HeaderContentType, echo.MIMEApplicationJSON)

	resp, err := Client.Do(req)
	if err != nil {
		return nil, serror.New(http.StatusInternalServerError, err)
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, serror.New(http.StatusInternalServerError, err)
		}

		getResourcePoolStatusResponse := &response.GetResourcePoolStatusResponse{}
		if err := json.Unmarshal(b, getResourcePoolStatusResponse); err != nil {
			return nil, serror.New(http.StatusInternalServerError, err)
		}
		return getResourcePoolStatusResponse, nil
	default:
		return nil, r.handleStatusError(resp, endpoint)
	}
}

func (r *rmsRPCService) GetTestbedResourceStatus(endpoint, token, resourcePool string) (*response.GetTestbedsResourceResponse, *serror.Error) {
	req, _ := http.NewRequest(http.MethodGet, endpoint+"/api/v1/resource-pool/testbeds-resource", bytes.NewReader(
		utils.ToJSON(request.GetResourcePoolStatusRequest{
			ResourcePool: resourcePool,
		})))
	req.Header.Add(echo.HeaderAuthorization, token)
	req.Header.Add(echo.HeaderContentType, echo.MIMEApplicationJSON)

	resp, err := Client.Do(req)
	if err != nil {
		return nil, serror.New(http.StatusInternalServerError, err)
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, serror.New(http.StatusInternalServerError, err)
		}

		getTestbedsResourceResponse := &response.GetTestbedsResourceResponse{}
		if err := json.Unmarshal(b, getTestbedsResourceResponse); err != nil {
			return nil, serror.New(http.StatusInternalServerError, err)
		}
		return getTestbedsResourceResponse, nil
	default:
		return nil, r.handleStatusError(resp, endpoint)
	}
}

func (r *rmsRPCService) ListTestBeds(endpoint, token string) (*response.ListTestbedResponse, *serror.Error) {
	req, _ := http.NewRequest("GET", endpoint+"/api/v1/testbeds", nil)
	req.Header.Add(echo.HeaderAuthorization, token)

	resp, err := Client.Do(req)
	if err != nil {
		return nil, serror.New(http.StatusInternalServerError, err)
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, serror.New(http.StatusInternalServerError, err)
		}

		listTestbedResponse := &response.ListTestbedResponse{}
		if err := json.Unmarshal(b, listTestbedResponse); err != nil {
			return nil, serror.New(http.StatusInternalServerError, err)
		}
		return listTestbedResponse, nil
	default:
		return nil, r.handleStatusError(resp, endpoint)
	}
}

func (r *rmsRPCService) GetTestBed(testbedURL, token string, withResourceDetails bool) (*response.GetTestbedResponse, *serror.Error) {
	if withResourceDetails {
		testbedURL = testbedURL + "?with_details=true"
	}
	req, _ := http.NewRequest("GET", testbedURL, nil)
	req.Header.Add(echo.HeaderAuthorization, token)

	resp, err := Client.Do(req)
	if err != nil {
		return nil, serror.New(http.StatusInternalServerError, err)
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, serror.New(http.StatusInternalServerError, fmt.Errorf("bad http status %d: %s, body err: %w", resp.StatusCode, req.URL.String(), err))
		}

		testbedResponse := &response.GetTestbedResponse{}
		if err := json.Unmarshal(b, testbedResponse); err != nil {
			return nil, serror.New(http.StatusInternalServerError, err)
		}
		return testbedResponse, nil
	default:
		return nil, r.handleStatusError(resp, testbedURL)
	}
}

func (r *rmsRPCService) RenewTestBed(lease, testbedURL, token string) *serror.Error {
	req, _ := http.NewRequest("PUT", testbedURL+"/lease/renew?lease="+lease, nil)
	req.Header.Add(echo.HeaderAuthorization, token)

	resp, err := Client.Do(req)
	if err != nil {
		return serror.New(http.StatusInternalServerError, err)
	}
	defer resp.Body.Close()

	return r.handleStatusError(resp, req.URL.String())
}

func (r *rmsRPCService) ReleaseTestBed(testbedURL, token string) *serror.Error {
	req, _ := http.NewRequest("DELETE", testbedURL, nil)
	req.Header.Add(echo.HeaderAuthorization, token)

	resp, err := Client.Do(req)
	if err != nil {
		return serror.New(http.StatusInternalServerError, err)
	}
	defer resp.Body.Close()

	return r.handleStatusError(resp, req.URL.String())
}

func (r *rmsRPCService) RestoreMonitor(testbed, clusterType, testbedURL, promTarballURL, token string) (*response.RestoreMonitorResponse, *serror.Error) {
	rawReq, err := json.Marshal(request.RestoreMonitorRequest{
		Testbed:              testbed,
		ClusterType:          clusterType,
		PrometheusTarballURL: promTarballURL,
	})
	if err != nil {
		return nil, serror.New(http.StatusInternalServerError, err)
	}

	req, _ := http.NewRequest("POST", testbedURL+"/restore-monitor", bytes.NewReader(rawReq))
	req.Header.Add(echo.HeaderContentType, echo.MIMEApplicationJSON)
	req.Header.Add(echo.HeaderAuthorization, token)

	resp, err := Client.Do(req)
	if err != nil {
		return nil, serror.New(http.StatusInternalServerError, err)
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, serror.New(http.StatusInternalServerError, fmt.Errorf("bad http status %d: %s, body err: %w", resp.StatusCode, req.URL.String(), err))
		}

		restoreMonitorResponse := &response.RestoreMonitorResponse{}
		if err := json.Unmarshal(b, restoreMonitorResponse); err != nil {
			return nil, serror.New(http.StatusInternalServerError, err)
		}
		return restoreMonitorResponse, nil
	default:
		return nil, r.handleStatusError(resp, testbedURL)
	}
}

func (r *rmsRPCService) handleStatusError(resp *http.Response, url string) *serror.Error {
	if resp.StatusCode == http.StatusOK {
		return nil
	}
	statusError := new(errors.StatusError)
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return serror.New(http.StatusInternalServerError, fmt.Errorf("bad http status %d: %s, body err: %w", resp.StatusCode, url, err))
	}
	if err := json.Unmarshal(body, statusError); err != nil {
		return serror.New(http.StatusInternalServerError, fmt.Errorf("bad http status %d: %s, body unmarshal err: %w", resp.StatusCode, url, err))
	}
	statusError.Message = "RMS error: " + statusError.Message
	return serror.New(int(statusError.Code), statusError)
}
