package localserver

import (
	"bytes"
	"context"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/google/uuid"
)

var NodeToken string
var NodeID string
var HTTPClient *http.Client

type LocalServerConfig struct {
	configFilePath string
	NodeId         string `json:"nodeId"`
	PrivateKeyPath string `json:"privateKeyPath"`
	Token          string `json:"token"`
}

var configPath = ""
var certificatePath = ""

func NewLocalServerConfig(appCertificatePath, appConfigPath string) (localServerConfig *LocalServerConfig, err error) {
	HTTPClient = &http.Client{
		Transport: &http.Transport{
			DisableKeepAlives:   true,
			MaxIdleConns:        1,
			MaxIdleConnsPerHost: 1,
			MaxConnsPerHost:     1,
		},
		Timeout: 10 * time.Second,
	}
	configPath = appConfigPath
	certificatePath = appCertificatePath
	logFile, err := os.OpenFile(filepath.Join(configPath, "local_server.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
	if err != nil {
		return
	}
	logger.SetOutput(logFile)
	logger.SetFlags(log.Ldate | log.LUTC | log.Lshortfile | log.Ltime | log.LstdFlags | log.Lmsgprefix)
	if _, err = os.Stat(filepath.Join(configPath, "config")); err != nil {
		if os.IsNotExist(err) {
			if err = os.MkdirAll(filepath.Join(configPath, "config"), 0770); err != nil {
				return
			}
		} else {
			return
		}
	}
	localServerConfig = &LocalServerConfig{
		configFilePath: filepath.Join(configPath, "config", "node_config.json"),
	}
	err = localServerConfig.startup()
	if err != nil {
		return
	}
	err = localServerConfig.authenticate()
	return
}

func (l *LocalServerConfig) startup() (err error) {
	if _, err = os.Stat(l.configFilePath); os.IsNotExist(err) {
		file, fileErr := os.Create(l.configFilePath)
		if fileErr != nil {
			return fileErr
		}
		id := uuid.NewString()
		e := NewEncryptionManager()
		keypair, keyErr := e.GenerateKeyPair(context.Background(), id, "")
		if keyErr != nil {
			return keyErr
		}
		baseConfig := map[string]interface{}{
			"nodeId":      id,
			"privKeyPath": filepath.Join(configPath, "config", id),
			"token":       "",
		}
		bs, marshallErr := json.Marshal(&baseConfig)
		if marshallErr != nil {
			return marshallErr
		}
		_, writeErr := file.Write(bs)
		if writeErr != nil {
			return writeErr
		}
		key_bytes := x509.MarshalPKCS1PublicKey(&keypair.PublicKey)
		key := pem.EncodeToMemory(&pem.Block{
			Type:  "RSA PUBLIC KEY",
			Bytes: key_bytes,
		})
		logger.Println(string(key))
		l.NodeId = id
		l.PrivateKeyPath = ""
		l.Token = ""
		bss, marshallErr := json.Marshal(map[string]interface{}{
			"type":     "create_node",
			"from":     id,
			"to":       "serv",
			"token":    "",
			"peerType": "node",
			"payload": map[string]string{
				"nodeId":       id,
				"nodeKey":      string(key),
				"nodeUsername": "anonymous",
			},
		})
		if marshallErr != nil {
			return marshallErr
		}
		res, postErr := HTTPClient.Post("https://dev.zippytal.com/req", "application/json", bytes.NewBuffer(bss))
		if postErr != nil {
			logger.Println("e")
			return postErr
		}
		logger.Println(res)
		if err = file.Close(); err != nil {
			return
		}
	} else if err != nil {
		return
	}
	file, err := os.Open(l.configFilePath)
	defer file.Close()
	if err != nil {
		return
	}
	var config map[string]string
	err = json.NewDecoder(file).Decode(&config)
	if err != nil {
		return
	}
	logger.Println(config)
	NodeID = l.NodeId
	l.NodeId = config["nodeId"]
	l.PrivateKeyPath = config["privKeyPath"]
	l.Token = config["token"]
	return
}

func (l *LocalServerConfig) authenticate() (err error) {
	f, err := os.Open(l.PrivateKeyPath)
	defer f.Close()
	if err != nil {
		return
	}
	var buff []byte
	buff, err = io.ReadAll(f)
	if err != nil {
		return
	}
	em := NewEncryptionManager()
	sig, err := em.SignRequest(string(buff), l.NodeId)
	if err != nil {
		return
	}
	body, err := json.Marshal(map[string]interface{}{
		"type":     "login_key_node",
		"from":     l.NodeId,
		"peerType": "node",
		"payload": map[string]string{
			"id":        l.NodeId,
			"signature": sig,
		},
	})
	if err != nil {
		return
	}
	res, err := HTTPClient.Post("https://dev.zippytal.com/req", "application/json", bytes.NewBuffer(body))
	if err != nil {
		return
	}
	bs, err := io.ReadAll(res.Body)
	if err != nil {
		return
	}
	var payload map[string]any
	if err = json.Unmarshal(bs, &payload); err != nil {
		return
	}
	err = l.handleLoginResponse(payload)
	NodeID = l.NodeId
	return
}

func (l *LocalServerConfig) handleLoginResponse(res map[string]any) (err error) {
	if _, ok := res["token"]; !ok {
		err = fmt.Errorf("no field token in res")
		return
	}
	if _, ok := res["token"].(string); !ok {
		err = fmt.Errorf("field token not string in res")
		return
	}
	NodeToken = res["token"].(string)
	return
}
