package rest_websocket

import (
	"context"
	"log"
	"sync"
	"time"

	"git.ali33.ru/fcg-xvii/go-tools/json"
	"git.ali33.ru/fcg-xvii/rest"
	"github.com/gorilla/websocket"
)

func newWebSocket(conn *websocket.Conn, appConf *appConfig) *WebSocket {
	ctx, cancel := context.WithCancel(context.Background())
	ws := &WebSocket{
		conn:              conn,
		waitMessages:      make(map[int64]*MessageIncoming),
		ctx:               ctx,
		cancel:            cancel,
		sendLocker:        &sync.Mutex{},
		appConf:           appConf,
		chMessageIncoming: make(chan *MessageIncoming, 10),
	}
	go ws.read()
	return ws
}

type WebSocket struct {
	conn              *websocket.Conn
	waitMessages      map[int64]*MessageIncoming
	ctx               context.Context
	cancel            context.CancelFunc
	sendLocker        *sync.Mutex
	appConf           *appConfig
	chMessageIncoming chan *MessageIncoming
	Auth              json.Map
	Data              json.Map
}

func (s *WebSocket) MessagesIncoming() <-chan *MessageIncoming {
	return s.chMessageIncoming
}

func (s *WebSocket) Close() {

}

func (s *WebSocket) sendMessage(msg *Message) rest.IErrorArgs {
	s.sendLocker.Lock()
	writer, err := s.conn.NextWriter(websocket.TextMessage)
	s.sendLocker.Unlock()
	if err != nil {
		return rest.ErrorMessage("ErrWriterInit", err.Error())
	}
	err = msg.Write(writer)
	writer.Close()
	return nil
}

func (s *WebSocket) execMessage(mes *Message) {
	defer mes.Close()
	log.Println("Message", mes)
	req := &Request{
		mes: mes,
	}
	var resp rest.IResponse
	command, check := s.appConf.app.Executer(req)
	if !check {
		resp = req.ResponseError(404, rest.ErrorMessage("ErrNotFound", "command is not found"))
	} else {
		// serialize
		if err := rest.Serialize(mes.data, command); err != nil {
			log.Println("serialize error", err)
			return
		}
		// validate
		if validator, check := command.(rest.IValidator); check {
			resp := validator.Validate(req)
			if resp != nil {
				if err := resp.Send(nil); err != nil {
					log.Println("socket send error", err.Map())
				}
				return
			}
		}
		resp = command.Execute(req)
	}
	log.Println("RESP", resp)
	resp.Send(nil)
	resp.Close()
}

func (s *WebSocket) messageIncoming() chan<- *Message {
	chIncoming := make(chan *Message, 100)
	tClean := time.NewTicker(time.Second * 60)
	defer tClean.Stop()
	go func() {
		for {
			select {
			case <-s.ctx.Done():
				return
			case mes, ok := <-chIncoming:
				if !ok {
					return
				}
				mes.owner = s
				log.Println("INCOMING!!!!!")
				switch mes.mType {
				case rest.RequestTypeMessage, rest.RequestTypeEvent:
					s.execMessage(mes)
				case rest.RequestTypeAnswer:
					log.Println("ANSWER")
				}
			case <-tClean.C:
				now := time.Now()
				for id, mes := range s.waitMessages {
					if mes.timeout.Before(now) {
						delete(s.waitMessages, id)
					}
				}
			}
		}
	}()
	return chIncoming
}

func (s *WebSocket) read() {
	chIncoming := s.messageIncoming()
	defer s.cancel()
	for {
		// Read message from server
		mType, r, err := s.conn.NextReader()
		if err != nil {
			log.Println(err)
			return
		}
		switch mType {
		case websocket.TextMessage, websocket.BinaryMessage:
			// Обработка текстового сообщения
			mes, err := ReadMessage(r)
			if err != nil {
				log.Println("data error: ", err)
				return
			}
			chIncoming <- mes
		case websocket.PingMessage:
			// Отправка Pong в ответ на Ping
			s.sendLocker.Lock()
			err := s.conn.WriteControl(websocket.PongMessage, nil, time.Now().Add(time.Second))
			s.sendLocker.Unlock()
			if err != nil {
				log.Println("pong write:", err)
				return
			}
		case websocket.CloseMessage:
			// Обработка закрытия соединения
			log.Println("websocket connection closed")
			return
		}
	}
}
