package vehicleconfig

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/pkg/errors"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/common/staticerrors"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
)

func NewSAPService() SAPServiceInterface {
	return &SAPService{
		sapUser: envtool.GetEnv("SAP_USER", "REPLACE_ME"),
		sapPass: envtool.GetEnv("SAP_PASS", "REPLACE_ME"),
		sapURL:  envtool.GetEnv("SAP_URL", "REPLACE_ME"),
	}
}

type SAPServiceInterface interface {
	GetSAPOrder(vin string) (order common.VehicleOrder, err error)
	GetECUVersions(vin string) (versions map[string]string, err error)
	UpdateECUVersions(vin string, versions map[string]string) (err error)
	GetFeatureCodes(vin string) (common.SAPResponse, error)
	SubmitResult(vin string, success bool) (err error)
	SubmitCarFlashpackVersion(vin string, previousFlashpack string, flashpack string) error
}

// This is currently a mock of the actual calls to the SAP and the ODX parser services
type SAPService struct {
	sapUser string
	sapPass string
	sapURL  string
}

type SAPSubmitResultBody struct {
	VIN      string `json:"vin" validate:"required,vin"`
	DateTime string `json:"dateTime"`
	Flag     string `json:"flag"`
}

type SAPSubmitCarFlashpackVersionBody struct {
	VIN         string `json:"vin" validate:"required,vin"`
	CreatedOn   string `json:"createdOn"`
	CurrentSWV  string `json:"currentSWV"`
	PreviousSWV string `json:"previousSWV"`
	ModeOfSWU   string `json:"modeOfSWU"`
}

// Returns the current vehicle order from SAP
func (cs *SAPService) GetSAPOrder(vin string) (order common.VehicleOrder, err error) {
	var result common.OrderUpdated
	err = xml.Unmarshal([]byte(vehicleOrder), &result)
	if err != nil {
		return
	}

	order = result.VehicleOrder
	return
}

// Returns the current ECU versions for a car
func (cs *SAPService) GetECUVersions(vin string) (versions map[string]string, err error) {
	versions = map[string]string{}
	for _, ecu := range ecus {
		versions[ecu] = "10000"
	}
	return
}

// Submits the updated ECU versions for a car back to SAP
func (cs *SAPService) UpdateECUVersions(vin string, versions map[string]string) (err error) {

	return
}

func (cs *SAPService) GetFeatureCodes(vin string) (common.SAPResponse, error) {
	headers := http.Header{}
	headers.Add("Authorization", cs.createToken())
	url := fmt.Sprintf("%s/vehicleSpec?vin=%s", cs.sapURL, vin)

	request, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return common.SAPResponse{}, errors.WithStack(err)
	}

	client := createHttpClient()
	request.Header = headers
	resp, err := client.Do(request)
	if err != nil {
		return common.SAPResponse{}, errors.WithStack(err)
	}

	if resp.StatusCode != http.StatusOK {
		return common.SAPResponse{}, errors.WithStack(fmt.Errorf(staticerrors.ErrorSAPFailedCall+" with status: %s", resp.Status))
	}

	defer resp.Body.Close()
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return common.SAPResponse{}, errors.WithStack(err)
	}

	respPayload := common.SAPResponse{}
	err = json.Unmarshal(respBody, &respPayload)
	if err != nil {
		return common.SAPResponse{}, errors.WithStack(err)
	}

	return respPayload, nil
}

func (cs *SAPService) SubmitResult(vin string, success bool) (err error) {
	headers := http.Header{}
	headers.Add("Authorization", cs.createToken())
	headers.Add("Content-Type", "application/json")

	timeNow := time.Now()
	formattedTime := timeNow.Format("2006-01-02T15:04:05")

	body := &SAPSubmitResultBody{
		VIN:      vin,
		DateTime: formattedTime,
		Flag:     "not completed",
	}
	if success {
		body.Flag = "completed"
	}

	bodyJSON, err := json.Marshal(body)
	if err != nil {
		return errors.WithStack(err)
	}

	return cs.submit("vehicleSpecAck", bodyJSON, http.StatusOK)
}

func (cs *SAPService) SubmitCarFlashpackVersion(vin string, previousFlashpack string, flashpack string) error {
	timeNow := time.Now()
	formattedTime := timeNow.Format("2006-01-02T15:04:05.999999Z")
	body := &SAPSubmitCarFlashpackVersionBody{
		VIN:         vin,
		CreatedOn:   formattedTime,
		PreviousSWV: previousFlashpack,
		CurrentSWV:  flashpack,
		ModeOfSWU:   "OTA",
	}

	bodyJSON, err := json.Marshal(body)
	if err != nil {
		return errors.WithStack(err)
	}

	return cs.submit("swVersionUpdate", bodyJSON, http.StatusCreated)
}

func (cs *SAPService) submit(endpoint string, bodyJSON []byte, successStatus int) error {
	headers := http.Header{}
	headers.Add("Authorization", cs.createToken())
	headers.Add("Content-Type", "application/json")
	url := fmt.Sprintf("%s/%s", cs.sapURL, endpoint)

	request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(bodyJSON))
	if err != nil {
		return errors.WithStack(err)
	}

	client := createHttpClient()
	request.Header = headers
	resp, err := client.Do(request)
	if err != nil {
		return errors.WithStack(err)
	}

	if resp.StatusCode != successStatus {
		return errors.WithStack(fmt.Errorf(staticerrors.ErrorSAPFailedCall+" with status: %s and payload: %s", resp.Status, string(bodyJSON)))
	}

	defer resp.Body.Close()
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return errors.WithStack(err)
	}

	logger.Info().Msg(fmt.Sprintf("sap service has been notified at %s of car update: %s", endpoint, string(respBody)))

	return nil
}

func (cs *SAPService) createToken() string {
	token := base64.StdEncoding.EncodeToString([]byte(cs.sapUser + ":" + cs.sapPass))
	return fmt.Sprintf("Basic %s", token)
}

func createHttpClient() http.Client {
	transport := http.DefaultTransport.(*http.Transport)
	transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	return http.Client{
		Transport: transport,
	}
}
