package lakebtc

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/thrasher-corp/gocryptotrader/common"
	"github.com/thrasher-corp/gocryptotrader/currency"
	"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
	"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
	"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
	"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
	"github.com/thrasher-corp/gocryptotrader/log"
	"github.com/toorop/go-pusher"
)

const (
	lakeBTCWSURL         = "wss://ws.lakebtc.com:8085"
	marketGlobalEndpoint = "market-global"
	marketSubstring      = "market-"
	globalSubstring      = "-global"
	wssSchem             = "wss"
)

// WsConnect initiates a new websocket connection
func (l *LakeBTC) WsConnect() error {
	if !l.Websocket.IsEnabled() || !l.IsEnabled() {
		return errors.New(stream.WebsocketNotEnabled)
	}

	url := strings.Split(lakeBTCWSURL, "://")
	var err error
	l.WebsocketConn.Client, err = pusher.NewCustomClient(strings.ToLower(l.Name),
		url[1],
		wssSchem)
	if err != nil {
		return err
	}
	err = l.WebsocketConn.Client.Subscribe(marketGlobalEndpoint)
	if err != nil {
		return err
	}

	err = l.listenToEndpoints()
	if err != nil {
		return err
	}
	go l.wsHandleIncomingData()
	return nil
}

func (l *LakeBTC) listenToEndpoints() error {
	var err error
	l.WebsocketConn.Ticker, err = l.WebsocketConn.Client.Bind("tickers")
	if err != nil {
		return fmt.Errorf("%s Websocket Bind error: %s", l.Name, err)
	}
	l.WebsocketConn.Orderbook, err = l.WebsocketConn.Client.Bind("update")
	if err != nil {
		return fmt.Errorf("%s Websocket Bind error: %s", l.Name, err)
	}
	// LakeBTC does not provide enough trade data to sync to the trade database table
	// please use REST until the API is updated
	// l.WebsocketConn.Trade, err = l.WebsocketConn.Client.Bind("trades")
	return nil
}

// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (l *LakeBTC) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
	var subscriptions []stream.ChannelSubscription
	enabledCurrencies, err := l.GetEnabledPairs(asset.Spot)
	if err != nil {
		return nil, err
	}

	for j := range enabledCurrencies {
		enabledCurrencies[j].Delimiter = ""
		channel := marketSubstring +
			enabledCurrencies[j].Lower().String() +
			globalSubstring

		subscriptions = append(subscriptions, stream.ChannelSubscription{
			Channel:  channel,
			Currency: enabledCurrencies[j],
			Asset:    asset.Spot,
		})
	}
	return subscriptions, nil
}

// Subscribe sends a websocket message to receive data from the channel
func (l *LakeBTC) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
	var errs common.Errors
	for i := range channelsToSubscribe {
		err := l.WebsocketConn.Client.Subscribe(channelsToSubscribe[i].Channel)
		if err != nil {
			errs = append(errs, err)
			continue
		}
		l.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
	}
	if errs != nil {
		return errs
	}
	return nil
}

// Unsubscribe sends a websocket message to unsubscribe from the channel
func (l *LakeBTC) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
	var errs common.Errors
	for i := range channelsToUnsubscribe {
		err := l.WebsocketConn.Client.Unsubscribe(channelsToUnsubscribe[i].Channel)
		if err != nil {
			errs = append(errs, err)
			continue
		}
		l.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
	}
	if errs != nil {
		return errs
	}
	return nil
}

// wsHandleIncomingData services incoming data from the websocket connection
func (l *LakeBTC) wsHandleIncomingData() {
	l.Websocket.Wg.Add(1)
	defer l.Websocket.Wg.Done()

	for {
		select {
		case <-l.Websocket.ShutdownC:
			return
		case data := <-l.WebsocketConn.Ticker:
			if l.Verbose {
				log.Debugf(log.ExchangeSys,
					"%v Websocket message received: %v", l.Name, data)
			}
			err := l.processTicker(data.Data)
			if err != nil {
				l.Websocket.DataHandler <- err
			}
		case data := <-l.WebsocketConn.Orderbook:
			if l.Verbose {
				log.Debugf(log.ExchangeSys,
					"%v Websocket message received: %v", l.Name, data)
			}
			err := l.processOrderbook(data.Data, data.Channel)
			if err != nil {
				l.Websocket.DataHandler <- err
			}
		}
		select {
		case l.Websocket.TrafficAlert <- struct{}{}:
		default:
		}
	}
}

func (l *LakeBTC) processOrderbook(obUpdate, channel string) error {
	var update WsOrderbookUpdate
	err := json.Unmarshal([]byte(obUpdate), &update)
	if err != nil {
		return err
	}

	p, err := l.getCurrencyFromChannel(channel)
	if err != nil {
		return err
	}

	book := orderbook.Base{
		Pair:            p,
		LastUpdated:     time.Now(),
		Asset:           asset.Spot,
		Exchange:        l.Name,
		VerifyOrderbook: l.CanVerifyOrderbook,
	}

	for i := range update.Asks {
		var amount, price float64
		amount, err = strconv.ParseFloat(update.Asks[i][1], 64)
		if err != nil {
			return err
		}
		price, err = strconv.ParseFloat(update.Asks[i][0], 64)
		if err != nil {
			return err
		}
		book.Asks = append(book.Asks, orderbook.Item{
			Amount: amount,
			Price:  price,
		})
	}

	for i := range update.Bids {
		var amount, price float64
		amount, err = strconv.ParseFloat(update.Bids[i][1], 64)
		if err != nil {
			return err
		}
		price, err = strconv.ParseFloat(update.Bids[i][0], 64)
		if err != nil {
			return err
		}
		book.Bids = append(book.Bids, orderbook.Item{
			Amount: amount,
			Price:  price,
		})
	}

	return l.Websocket.Orderbook.LoadSnapshot(&book)
}

func (l *LakeBTC) getCurrencyFromChannel(channel string) (currency.Pair, error) {
	curr := strings.Replace(channel, marketSubstring, "", 1)
	curr = strings.Replace(curr, globalSubstring, "", 1)
	return currency.NewPairFromString(curr)
}

func (l *LakeBTC) processTicker(data string) error {
	var tUpdate map[string]wsTicker
	err := json.Unmarshal([]byte(data), &tUpdate)
	if err != nil {
		l.Websocket.DataHandler <- err
		return err
	}

	enabled, err := l.GetEnabledPairs(asset.Spot)
	if err != nil {
		return err
	}

	for k, v := range tUpdate {
		returnCurrency, err := currency.NewPairFromString(k)
		if err != nil {
			return err
		}

		if !enabled.Contains(returnCurrency, true) {
			continue
		}

		l.Websocket.DataHandler <- &ticker.Price{
			ExchangeName: l.Name,
			Bid:          v.Buy,
			High:         v.High,
			Last:         v.Last,
			Low:          v.Low,
			Ask:          v.Sell,
			Volume:       v.Volume,
			AssetType:    asset.Spot,
			Pair:         returnCurrency,
		}
	}
	return nil
}
