package bsteam

import (
	"fmt"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/Philipp15b/go-steam"
	"github.com/Philipp15b/go-steam/protocol/steamlang"
	"github.com/Philipp15b/go-steam/steamid"
	//"io/ioutil"
	"strconv"
	"sync"
	"time"
)

type Bsteam struct {
	c         *steam.Client
	connected chan struct{}
	userMap   map[steamid.SteamId]string
	sync.RWMutex
	*bridge.Config
}

func New(cfg *bridge.Config) bridge.Bridger {
	b := &Bsteam{Config: cfg}
	b.userMap = make(map[steamid.SteamId]string)
	b.connected = make(chan struct{})
	return b
}

func (b *Bsteam) Connect() error {
	b.Log.Info("Connecting")
	b.c = steam.NewClient()
	go b.handleEvents()
	go b.c.Connect()
	select {
	case <-b.connected:
		b.Log.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
	}

	// Handle files
	if msg.Extra != nil {
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
			b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, rmsg.Username+rmsg.Text)
		}
		if len(msg.Extra["file"]) > 0 {
			for _, f := range msg.Extra["file"] {
				fi := f.(config.FileInfo)
				if fi.Comment != "" {
					msg.Text += fi.Comment + ": "
				}
				if fi.URL != "" {
					msg.Text = fi.URL
					if fi.Comment != "" {
						msg.Text = fi.Comment + ": " + fi.URL
					}
				}
				b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
			}
			return "", nil
		}
	}

	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.GetString("Login")
	myLoginInfo.Password = b.GetString("Password")
	myLoginInfo.AuthCode = b.GetString("AuthCode")
	// Attempt to read existing auth hash to avoid steam guard.
	// Maybe works
	//myLoginInfo.SentryFileHash, _ = ioutil.ReadFile("sentry")
	for event := range b.c.Events() {
		//b.Log.Info(event)
		switch e := event.(type) {
		case *steam.ChatMsgEvent:
			b.Log.Debugf("Receiving ChatMsgEvent: %#v", e)
			b.Log.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:
			b.Log.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:
			/*
				b.Log.Info("authupdate", e)
				b.Log.Info("hash", e.Hash)
				ioutil.WriteFile("sentry", e.Hash, 0666)
			*/
		case *steam.LogOnFailedEvent:
			b.Log.Info("Logon failed", e)
			switch e.Result {
			case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode:
				{
					b.Log.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:
				{
					b.Log.Info("Steam guard isn't letting me in! Enter auth code:")
					var code string
					fmt.Scanf("%s", &code)
					myLoginInfo.AuthCode = code
				}
			default:
				b.Log.Errorf("LogOnFailedEvent: %#v ", e.Result)
				// TODO: Handle EResult_InvalidLoginAuthCode
				return
			}
		case *steam.LoggedOnEvent:
			b.Log.Debugf("LoggedOnEvent: %#v", e)
			b.connected <- struct{}{}
			b.Log.Debugf("setting online")
			b.c.Social.SetPersonaState(steamlang.EPersonaState_Online)
		case *steam.DisconnectedEvent:
			b.Log.Info("Disconnected")
			b.Log.Info("Attempting to reconnect...")
			b.c.Connect()
		case steam.FatalErrorEvent:
			b.Log.Error(e)
		case error:
			b.Log.Error(e)
		default:
			b.Log.Debugf("unknown event %#v", e)
		}
	}
}
