package cl_104

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/influxdata/telegraf/internal"
)

func (h *CL104) connectingCLAndParse(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			return
		case <-h.close:
			return
		default:
			newCtx, newCancel := context.WithCancel(ctx)
			h.newConnectCLAndParse(newCtx, newCancel)
			time.Sleep(time.Second * 5)
		}
	}
}

func (h *CL104) newConnectCLAndParse(ctx context.Context, cancel context.CancelFunc) {
	c, _, err := websocket.DefaultDialer.DialContext(ctx, h.CLURL, nil)
	if err != nil {
		h.Log.Error("client dial:", err)
		return
	}
	defer c.Close()

	session := &wsSession{
		conn:   c,
		ctx:    ctx,
		cancel: cancel,
		ctrlCh: make(chan wsMsg, 2),
	}

	if err := h.setupSessionConn(session); err != nil {
		h.Log.Error(err)
		return
	}

	h.startClWorkersWithChAndParse(session)
}

func (h *CL104) startClWorkers(session *wsSession) {
	upChan := make(chan []byte, 16)
	clChan := make(chan []byte, 16)
	h.startClWorkersWithCh(session, upChan, clChan)
}

func (h *CL104) startClWorkersWithCh(session *wsSession, upChan, clChan chan []byte) {
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		h.monitorClWrite(session, clChan)
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		h.monitorClRead(session, upChan)
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		h.sendPing(session)
	}()

	wg.Wait()
}

func (h *CL104) startClWorkersWithChAndParse(session *wsSession) {
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		h.monitorClWriteAndParse(session)
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		h.monitorClReadAndParse(session)
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		h.sendPing(session)
	}()

	wg.Wait()
}

func (h *CL104) monitorClWrite(session *wsSession, clChan chan []byte) {
	var err error
	for {
		select {
		case <-session.ctx.Done():
			return
		case ctrl := <-session.ctrlCh:
			err = session.conn.SetWriteDeadline(time.Now().Add(time.Duration(h.WriteWait)))
			if err != nil {
				session.cancel()
				return
			}
			err = session.conn.WriteControl(ctrl.mt, ctrl.data, time.Now().Add(time.Duration(h.WriteWait)))
			if err != nil {
				session.cancel()
				return
			}
		case msg := <-clChan:
			err = session.conn.SetWriteDeadline(time.Now().Add(time.Duration(h.WriteWait)))
			if err != nil {
				session.cancel()
				return
			}
			err = session.conn.WriteMessage(websocket.TextMessage, msg)
			if err != nil {
				session.cancel()
				return
			}
		}
	}
}

func (h *CL104) monitorClWriteAndParse(session *wsSession) {
	var err error
	for {
		select {
		case <-session.ctx.Done():
			return
		case ctrl := <-session.ctrlCh:
			err = session.conn.SetWriteDeadline(time.Now().Add(time.Duration(h.WriteWait)))
			if err != nil {
				session.cancel()
				return
			}
			err = session.conn.WriteControl(ctrl.mt, ctrl.data, time.Now().Add(time.Duration(h.WriteWait)))
			if err != nil {
				session.cancel()
				return
			}
		}
	}
}

func (h *CL104) monitorClRead(session *wsSession, upChan chan []byte) {
	for {
		select {
		case <-session.ctx.Done():
			return
		default:
			mt, rm, err := session.conn.ReadMessage()
			if err != nil {
				if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
					session.cancel()
					return
				}
				if ce, ok := err.(*websocket.CloseError); ok {
					h.Log.Infof("client closed with code:", ce.Code, "text:", ce.Text)
					session.cancel()
					return
				}
				h.Log.Error("server read:", err)
				session.cancel()
				return
			}

			switch mt {
			case websocket.TextMessage:
				if err := h.fromClstream(session, rm, upChan); err != nil {
					h.Log.Error(err)
				}
			default:
				h.Log.Info("rm not text")
			}
		}
	}
}

func (h *CL104) monitorClReadAndParse(session *wsSession) {
	for {
		select {
		case <-session.ctx.Done():
			return
		default:
			mt, rm, err := session.conn.ReadMessage()
			if err != nil {
				if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
					session.cancel()
					return
				}
				if ce, ok := err.(*websocket.CloseError); ok {
					h.Log.Infof("client closed with code:", ce.Code, "text:", ce.Text)
					session.cancel()
					return
				}
				h.Log.Error("server read:", err)
				session.cancel()
				return
			}

			switch mt {
			case websocket.TextMessage:
				if err := h.fromClstreamAndParse(session, rm); err != nil {
					h.Log.Error(err)
				}
			default:
				h.Log.Info("rm not text")
			}
		}
	}
}

func (h *CL104) fromClstream(session *wsSession, m []byte, upChan chan []byte) error {
	msg := new(msg)
	if err := json.Unmarshal(m, msg); err != nil {
		return err
	}

	switch {
	case msg.TI >= 1 && msg.TI <= 40, msg.TI >= 110 && msg.TI <= 119:

	case msg.TI >= 45 && msg.TI <= 69, msg.TI >= 100 && msg.TI <= 109:
		select {
		case <-session.ctx.Done():
			return session.ctx.Err()
		default:
			msg.PN = msg.COT & 0x40
			msg.COT = msg.COT & 0x3F
			if m, err := json.Marshal(msg); err != nil {
				return err
			} else {
				select {
				case upChan <- m:
				case <-time.After(time.Second * 5):
					h.Log.Error("drop up msg:", msg)
				}
			}

		}

	default:
		return fmt.Errorf("invalid TI: %d", msg.TI)
	}

	return nil
}

func (h *CL104) fromClstreamAndParse(session *wsSession, m []byte) error {
	msg := new(msg)
	if err := json.Unmarshal(m, msg); err != nil {
		return err
	}

	switch {
	case msg.TI >= 1 && msg.TI <= 40, msg.TI >= 110 && msg.TI <= 119:
		metrics, err := h.Parse(m)
		if err != nil {
			h.Log.Debugf("parse error: %s", err.Error())
			return err
		}

		if len(metrics) == 0 {
			once.Do(func() {
				h.Log.Debug(internal.NoMetricsCreatedMsg)
			})
		}

		for _, m := range metrics {
			h.acc.AddMetric(m)
		}

	case msg.TI >= 45 && msg.TI <= 69, msg.TI >= 100 && msg.TI <= 109:

	default:
		return fmt.Errorf("invalid TI: %d", msg.TI)
	}

	return nil
}
