package websocket

import (
	"bytes"
	"encoding/json"
	"net/http"
	"primesys_producer/src/logger"
	onlinecommand "primesys_producer/src/protocol/online_command"
	"primesys_producer/src/store"
	"primesys_producer/src/utils"
	"time"

	"github.com/gorilla/websocket"
)

const (
	// Time allowed to write a message to peer
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer
	pongWait = 60 * time.Second

	pingPeriod = (pongWait * 9) / 10

	maxmessageSize = 512
)

var (
	newline = []byte{'\n'}
	space   = []byte{' '}
)

// to upgrade connection from https to socket
var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

type Client struct {
	// unique id for every user who gets connected
	ID string
	// hub in which all client will be registered
	hub *Hub

	// socket connection
	conn *websocket.Conn

	// device key
	Key string

	send chan []byte
}

func (c *Client) readPump() {

	logger.Info("Reached upto Readpump")

	defer func() {
		c.hub.unregister <- c
		c.conn.Close()
	}()

	c.conn.SetReadLimit(maxmessageSize)
	c.conn.SetReadDeadline(time.Now().Add(pongWait))
	c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })

	for {
		_, message, err := c.conn.ReadMessage()

		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				logger.Error("Error: %v", err)
			}
			break
		}

		message = bytes.TrimSpace(bytes.ReplaceAll(message, newline, space))
		var request DeviceCommandRequest

		if err := json.Unmarshal(message, &request); err != nil {
			logger.Error("Invalid json method: %v", err)
			resp, _ := json.Marshal(DeviceCommandResponse{
				Imei:     request.Imei,
				Response: "Request does not matched",
				Success:  false,
			})
			c.send <- resp
			continue
		}
		logger.Info(
			"Device command received: imei=%d serverflag=%s command=%s",
			request.Imei,
			request.ServerFlag,
			request.Command,
		)

		logger.Info("Key=%s message: %s", c.Key, message)

		conn, ok := store.GetDeviceConn(request.Imei)

		logger.Info("found ok %t", ok)

		if ok {
			cmdPacket, err := onlinecommand.EncodeCommandToTerminal(request.Command, request.ServerFlag)
			if err != nil {
				logger.Error("Command encoding packet failed %v", err)
				continue
			}
			packet := onlinecommand.EncodeCommandPacket(cmdPacket)
			logger.Info("imei=%d,command=%s,out=%s", request.Imei, request.Command, utils.HexUpper(packet))

			if _, err := conn.Write(packet); err != nil {
				logger.Error("imei=%d command write error: %v", request.Imei, err)
				continue
			}
			resp, _ := json.Marshal(DeviceCommandResponse{
				Imei:       request.Imei,
				ServerFlag: string(request.ServerFlag),
				Response:   "Command sent successfully",
				Success:    true,
			})
			c.send <- resp
		} else {
			logger.UniqueLog("Always here")
			resp, _ := json.Marshal(DeviceCommandResponse{
				Imei:       request.Imei,
				ServerFlag: request.ServerFlag,
				Response:   "Device Not Connected to Server",
				Success:    false,
			})
			c.send <- resp
		}

	}

}

func (c *Client) writePump() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		c.conn.Close()
	}()

	for {
		select {
		case message, ok := <-c.send:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				logger.Error("Error in message writing")
				return
			}
			w.Write(message)

			n := len(c.send)
			for i := 0; i < n; i++ {
				w.Write(newline)
				w.Write(<-c.send)
			}

			if err := w.Close(); err != nil {
				return
			}
		case <-ticker.C:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}

		}
	}
}

func serverWs(key string, hub *Hub, w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)

	if err != nil {
		logger.Error("Error %v", err)
		return
	}

	client := &Client{hub: hub, conn: conn, Key: key, send: make(chan []byte)}
	client.hub.register <- client

	go client.writePump()
	go client.readPump()
}
