package api

import (
	"GoSungrow/iSolarCloud/api/GoStruct"
	"GoSungrow/iSolarCloud/api/GoStruct/output"
	"github.com/MickMake/GoUnify/Only"
	"github.com/MickMake/GoUnify/cmdPath"
	"io"
	"path/filepath"
	"time"

	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
)


type Web struct {
	ServerUrl EndPointUrl
	Body      []byte
	Error     error

	cacheDir     string
	cacheTimeout time.Duration
	retry        int
	client       http.Client
	httpRequest  *http.Request
	httpResponse *http.Response
}


func (w *Web) SetUrl(u string) error {
	w.ServerUrl = SetUrl(u)
	return w.Error
}

func (w *Web) AppendUrl(endpoint string) EndPointUrl {
	return w.ServerUrl.AppendPath(endpoint)
}

func (w *Web) Get(endpoint EndPoint) EndPoint {
	for range Only.Once {
		w.Error = w.ServerUrl.IsValid()
		if w.Error != nil {
			w.Error = errors.New("Sungrow API EndPoint not yet implemented")
			fmt.Println(w.Error)
			break
		}

		isCached := false
		if w.WebCacheCheck(endpoint) {
			isCached = true
		}


		if isCached {
			w.Body, w.Error = w.WebCacheRead(endpoint)
			if w.Error != nil {
				break
			}

		} else {
			w.Body, w.Error = w.getApi(endpoint)
			if w.Error != nil {
				break
			}
		}


		if len(w.Body) == 0 {
			w.Error = errors.New("empty http response")
			break
		}
		endpoint = endpoint.SetResponse(w.Body)
		if endpoint.GetError() != nil {
			w.Error = endpoint.GetError()
			break
		}

		w.Error = endpoint.IsResponseValid()
		if w.Error != nil {
			_ = w.WebCacheRemove(endpoint)
			// fmt.Printf("ERROR: Body is:\n%s\n", w.Body)
			break
		}

		if isCached {
			// Do nothing.
		} else {
			w.Error = w.WebCacheWrite(endpoint, w.Body)
			if w.Error != nil {
				break
			}
		}
	}

	if w.Error != nil {
		endpoint = endpoint.SetError("%s", w.Error)
	}
	return endpoint
}

func (w *Web) getApi(endpoint EndPoint) ([]byte, error) {
	for range Only.Once {
		request := endpoint.RequestRef()
		w.Error = GoStruct.VerifyOptionsRequired(request)
		if w.Error != nil {
			break
		}

		w.Error = endpoint.IsRequestValid()
		if w.Error != nil {
			break
		}

		u := endpoint.GetUrl()
		w.Error = u.IsValid()
		if w.Error != nil {
			break
		}

		postUrl := w.ServerUrl.AppendPath(u.String()).String()
		var j []byte
		j, w.Error = json.Marshal(request)
		if w.Error != nil {
			break
		}

		w.httpResponse, w.Error = http.Post(postUrl, "application/json", bytes.NewBuffer(j))
		if w.Error != nil {
			break
		}

		if w.httpResponse.StatusCode == 401 {
			w.Error = errors.New(w.httpResponse.Status)
			break
		}

		//goland:noinspection GoUnhandledErrorResult,GoDeferInLoop
		defer w.httpResponse.Body.Close()
		if w.Error != nil {
			break
		}

		if w.httpResponse.StatusCode != 200 {
			w.Error = errors.New(fmt.Sprintf("API httpResponse is %s", w.httpResponse.Status))
			break
		}

		w.Body, w.Error = io.ReadAll(w.httpResponse.Body)
		if w.Error != nil {
			break
		}
	}

	return w.Body, w.Error
}

func (w *Web) SetCacheDir(basedir string) error {
	for range Only.Once {
		w.cacheDir = filepath.Join(basedir)

		p := cmdPath.NewPath(basedir)
		if p.DirExists() {
			break
		}

		w.Error = p.MkdirAll()
		if w.Error != nil {
			break
		}

		// _, w.Error = os.Stat(w.cacheDir)
		// if w.Error != nil {
		// 	if os.IsNotExist(w.Error) {
		// 		w.Error = nil
		// 	}
		// 	break
		// }
		//
		// w.Error = os.MkdirAll(w.cacheDir, 0700)
		// if w.Error != nil {
		// 	break
		// }
	}

	return w.Error
}

func (w *Web) GetCacheDir() string {
	return w.cacheDir
}

func (w *Web) SetCacheTimeout(duration time.Duration) {
	w.cacheTimeout = duration
}

func (w *Web) GetCacheTimeout() time.Duration {
	return w.cacheTimeout
}

// WebCacheCheck Retrieves cache data from a local file.
func (w *Web) WebCacheCheck(endpoint EndPoint) bool {
	var ok bool
	for range Only.Once {
		// fn := filepath.Join(w.cacheDir, endpoint.CacheFilename())
		//
		// var f os.FileInfo
		// f, w.Error = os.Stat(fn)
		// if w.Error != nil {
		// 	if os.IsNotExist(w.Error) {
		// 		w.Error = nil
		// 	}
		// 	break
		// }
		//
		// if f.IsDir() {
		// 	w.Error = errors.New("file is a directory")
		// 	break
		// }

		p := cmdPath.NewPath(w.cacheDir, endpoint.CacheFilename())
		if p.DirExists() {
			w.Error = errors.New("file is a directory")
			ok = false
			break
		}
		if !p.FileExists() {
			ok = false
			break
		}

		duration := w.GetCacheTimeout()
		then := p.ModTime()
		then = then.Add(duration)
		now := time.Now()
		if then.Before(now) {
			ok = false
			break
		}

		ok = true
	}

	return ok
}

// WebCacheRead Retrieves cache data from a local file.
func (w *Web) WebCacheRead(endpoint EndPoint) ([]byte, error) {
	fn := filepath.Join(w.cacheDir, endpoint.CacheFilename())
	return output.PlainFileRead(fn)
}

// WebCacheRemove Removes a cache file.
func (w *Web) WebCacheRemove(endpoint EndPoint) error {
	fn := filepath.Join(w.cacheDir, endpoint.CacheFilename())
	return output.FileRemove(fn)
}

// WebCacheWrite Saves cache data to a file path.
func (w *Web) WebCacheWrite(endpoint EndPoint, data []byte) error {
	fn := filepath.Join(w.cacheDir, endpoint.CacheFilename())
	return output.PlainFileWrite(fn, data, output.DefaultFileMode)
}


// PointCacheCheck Retrieves cache data from a local file.
func (w *Web) PointCacheCheck(data DataMap) bool {
	var ok bool
	for range Only.Once {
		p := cmdPath.NewPath(w.cacheDir, "Points.json")
		if p.DirExists() {
			w.Error = errors.New("file is a directory")
			ok = false
			break
		}
		if p.FileExists() {
			ok = true
			break
		}

		duration := w.GetCacheTimeout()
		then := p.ModTime()
		then = then.Add(duration)
		now := time.Now()
		if then.Before(now) {
			break
		}

		ok = true
	}

	return ok
}

// PointCacheRead Retrieves cache data from a local file.
func (w *Web) PointCacheRead(endpoint EndPoint) ([]byte, error) {
	fn := filepath.Join(w.cacheDir, endpoint.CacheFilename())
	return output.PlainFileRead(fn)
}

// PointCacheWrite Saves cache data to a file path.
func (w *Web) PointCacheWrite(endpoint EndPoint, data []byte) error {
	fn := filepath.Join(w.cacheDir, endpoint.CacheFilename())
	return output.PlainFileWrite(fn, data, output.DefaultFileMode)
}