package bsteam

import (
	"fmt"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/Philipp15b/go-steam"
	"github.com/Philipp15b/go-steam/protocol/steamlang"
	"github.com/Philipp15b/go-steam/steamid"
	log "github.com/Sirupsen/logrus"
	//"io/ioutil"
	"strconv"
	"sync"
	"time"
)

type Bsteam struct {
	c         *steam.Client
	connected chan struct{}
	Config    *config.Protocol
	Remote    chan config.Message
	Account   string
	userMap   map[steamid.SteamId]string
	sync.RWMutex
}

var flog *log.Entry
var protocol = "steam"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Bsteam {
	b := &Bsteam{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	b.userMap = make(map[steamid.SteamId]string)
	b.connected = make(chan struct{})
	return b
}

func (b *Bsteam) Connect() error {
	flog.Info("Connecting")
	b.c = steam.NewClient()
	go b.handleEvents()
	go b.c.Connect()
	select {
	case <-b.connected:
		flog.Info("Connection succeeded")
	case <-time.After(time.Second * 30):
		return fmt.Errorf("connection timed out")
	}
	return nil
}

func (b *Bsteam) Disconnect() error {
	b.c.Disconnect()
	return nil

}

func (b *Bsteam) JoinChannel(channel config.ChannelInfo) error {
	id, err := steamid.NewId(channel.Name)
	if err != nil {
		return err
	}
	b.c.Social.JoinChat(id)
	return nil
}

func (b *Bsteam) Send(msg config.Message) (string, error) {
	// ignore delete messages
	if msg.Event == config.EVENT_MSG_DELETE {
		return "", nil
	}
	id, err := steamid.NewId(msg.Channel)
	if err != nil {
		return "", err
	}
	b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
	return "", nil
}

func (b *Bsteam) getNick(id steamid.SteamId) string {
	b.RLock()
	defer b.RUnlock()
	if name, ok := b.userMap[id]; ok {
		return name
	}
	return "unknown"
}

func (b *Bsteam) handleEvents() {
	myLoginInfo := new(steam.LogOnDetails)
	myLoginInfo.Username = b.Config.Login
	myLoginInfo.Password = b.Config.Password
	myLoginInfo.AuthCode = b.Config.AuthCode
	// Attempt to read existing auth hash to avoid steam guard.
	// Maybe works
	//myLoginInfo.SentryFileHash, _ = ioutil.ReadFile("sentry")
	for event := range b.c.Events() {
		//flog.Info(event)
		switch e := event.(type) {
		case *steam.ChatMsgEvent:
			flog.Debugf("Receiving ChatMsgEvent: %#v", e)
			flog.Debugf("Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account)
			var channel int64
			if e.ChatRoomId == 0 {
				channel = int64(e.ChatterId)
			} else {
				// for some reason we have to remove 0x18000000000000
				channel = int64(e.ChatRoomId) - 0x18000000000000
			}
			msg := config.Message{Username: b.getNick(e.ChatterId), Text: e.Message, Channel: strconv.FormatInt(channel, 10), Account: b.Account, UserID: strconv.FormatInt(int64(e.ChatterId), 10)}
			b.Remote <- msg
		case *steam.PersonaStateEvent:
			flog.Debugf("PersonaStateEvent: %#v\n", e)
			b.Lock()
			b.userMap[e.FriendId] = e.Name
			b.Unlock()
		case *steam.ConnectedEvent:
			b.c.Auth.LogOn(myLoginInfo)
		case *steam.MachineAuthUpdateEvent:
			/*
				flog.Info("authupdate", e)
				flog.Info("hash", e.Hash)
				ioutil.WriteFile("sentry", e.Hash, 0666)
			*/
		case *steam.LogOnFailedEvent:
			flog.Info("Logon failed", e)
			switch e.Result {
			case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode:
				{
					flog.Info("Steam guard isn't letting me in! Enter 2FA code:")
					var code string
					fmt.Scanf("%s", &code)
					myLoginInfo.TwoFactorCode = code
				}
			case steamlang.EResult_AccountLogonDenied:
				{
					flog.Info("Steam guard isn't letting me in! Enter auth code:")
					var code string
					fmt.Scanf("%s", &code)
					myLoginInfo.AuthCode = code
				}
			default:
				log.Errorf("LogOnFailedEvent: %#v ", e.Result)
				// TODO: Handle EResult_InvalidLoginAuthCode
				return
			}
		case *steam.LoggedOnEvent:
			flog.Debugf("LoggedOnEvent: %#v", e)
			b.connected <- struct{}{}
			flog.Debugf("setting online")
			b.c.Social.SetPersonaState(steamlang.EPersonaState_Online)
		case *steam.DisconnectedEvent:
			flog.Info("Disconnected")
			flog.Info("Attempting to reconnect...")
			b.c.Connect()
		case steam.FatalErrorEvent:
			flog.Error(e)
		case error:
			flog.Error(e)
		default:
			flog.Debugf("unknown event %#v", e)
		}
	}
}
