package slackapp

import (
	"bytes"
	"encoding/json"
	"fmt"
	"html"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/nlopes/slack"
	"github.com/nlopes/slack/slackevents"

	"github.com/velour/catbase/bot"
	"github.com/velour/catbase/bot/msg"
	"github.com/velour/catbase/bot/user"
	"github.com/velour/catbase/config"
)

type SlackApp struct {
	bot    bot.Bot
	config *config.Config
	api    *slack.Client

	botToken     string
	userToken    string
	verification string
	id           string

	lastRecieved time.Time

	myBotID string
	users   map[string]string
	emoji   map[string]string

	event bot.Callback
}

func New(c *config.Config) *SlackApp {
	token := c.Get("slack.token", "NONE")
	if token == "NONE" {
		log.Fatalf("No slack token found. Set SLACKTOKEN env.")
	}

	api := slack.New(token, slack.OptionDebug(true))

	return &SlackApp{
		api:          api,
		config:       c,
		botToken:     token,
		userToken:    c.Get("slack.usertoken", "NONE"),
		verification: c.Get("slack.verification", "NONE"),
		myBotID:      c.Get("slack.botid", ""),
		lastRecieved: time.Now(),
		users:        make(map[string]string),
		emoji:        make(map[string]string),
	}
}

func (s *SlackApp) RegisterEvent(f bot.Callback) {
	s.event = f
}

func (s *SlackApp) Serve() error {
	s.populateEmojiList()

	http.HandleFunc("/evt", func(w http.ResponseWriter, r *http.Request) {
		buf := new(bytes.Buffer)
		buf.ReadFrom(r.Body)
		body := buf.String()
		eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: s.verification}))
		if e != nil {
			log.Println(e)
			w.WriteHeader(http.StatusInternalServerError)
		}

		if eventsAPIEvent.Type == slackevents.URLVerification {
			var r *slackevents.ChallengeResponse
			err := json.Unmarshal([]byte(body), &r)
			if err != nil {
				log.Println(err)
				w.WriteHeader(http.StatusInternalServerError)
			}
			w.Header().Set("Content-Type", "text")
			w.Write([]byte(r.Challenge))
		} else if eventsAPIEvent.Type == slackevents.CallbackEvent {
			innerEvent := eventsAPIEvent.InnerEvent
			switch ev := innerEvent.Data.(type) {
			case *slackevents.AppMentionEvent:
				// This is a bit of a problem. AppMentionEvent also needs to
				// End up in msgReceived
				//s.msgReceivd(ev)
			case *slackevents.MessageEvent:
				s.msgReceivd(ev)
			}
		} else {
			log.Printf("Event: (%v): %+v", eventsAPIEvent.Type, eventsAPIEvent)
		}
	})
	return nil
}

func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) {
	isItMe := msg.BotID != "" && msg.BotID == s.myBotID
	if !isItMe && msg.ThreadTimeStamp == "" {
		m := s.buildMessage(msg)
		if m.Time.Before(s.lastRecieved) {
			log.Printf("Ignoring message: lastRecieved: %v msg: %v", s.lastRecieved, m.Time)
		} else {
			s.lastRecieved = m.Time
			s.event(bot.Message, m)
		}
	} else if msg.ThreadTimeStamp != "" {
		//we're throwing away some information here by not parsing the correct reply object type, but that's okay
		s.event(bot.Reply, s.buildMessage(msg), msg.ThreadTimeStamp)
	} else {
		log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.Text)
	}
}

func (s *SlackApp) Send(kind bot.Kind, args ...interface{}) (string, error) {
	switch kind {
	case bot.Message:
		return s.sendMessage(args[0].(string), args[1].(string))
	case bot.Action:
		return s.sendAction(args[0].(string), args[1].(string))
	case bot.Edit:
		return s.edit(args[0].(string), args[1].(string), args[2].(string))
	case bot.Reply:
		switch args[2].(type) {
		case msg.Message:
			return s.replyToMessage(args[0].(string), args[1].(string), args[2].(msg.Message))
		case string:
			return s.replyToMessageIdentifier(args[0].(string), args[1].(string), args[2].(string))
		default:
			return "", fmt.Errorf("Invalid types given to Reply")
		}
	case bot.Reaction:
		return s.react(args[0].(string), args[1].(string), args[2].(msg.Message))
	default:
	}
	return "", fmt.Errorf("No handler for message type %d", kind)
}

func (s *SlackApp) sendMessageType(channel, message string, meMessage bool) (string, error) {
	ts, err := "", fmt.Errorf("")
	nick := s.config.Get("Nick", "bot")

	if meMessage {
		_, ts, err = s.api.PostMessage(channel,
			slack.MsgOptionUsername(nick),
			slack.MsgOptionText(message, false),
			slack.MsgOptionMeMessage())
	} else {
		_, ts, err = s.api.PostMessage(channel,
			slack.MsgOptionUsername(nick),
			slack.MsgOptionText(message, false))
	}

	if err != nil {
		log.Printf("Error sending message: %+v", err)
		return "", err
	}

	return ts, nil
}

func (s *SlackApp) sendMessage(channel, message string) (string, error) {
	log.Printf("Sending message to %s: %s", channel, message)
	identifier, err := s.sendMessageType(channel, message, false)
	return identifier, err
}

func (s *SlackApp) sendAction(channel, message string) (string, error) {
	log.Printf("Sending action to %s: %s", channel, message)
	identifier, err := s.sendMessageType(channel, "_"+message+"_", true)
	return identifier, err
}

func (s *SlackApp) replyToMessageIdentifier(channel, message, identifier string) (string, error) {
	nick := s.config.Get("Nick", "bot")
	icon := s.config.Get("IconURL", "https://placekitten.com/128/128")

	resp, err := http.PostForm("https://slack.com/api/chat.postMessage",
		url.Values{"token": {s.botToken},
			"username":  {nick},
			"icon_url":  {icon},
			"channel":   {channel},
			"text":      {message},
			"thread_ts": {identifier},
		})

	if err != nil {
		err := fmt.Errorf("Error sending Slack reply: %s", err)
		return "", err
	}

	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		err := fmt.Errorf("Error reading Slack API body: %s", err)
		return "", err
	}

	log.Println(string(body))

	type MessageResponse struct {
		OK        bool   `json:"ok"`
		Timestamp string `json:"ts"`
	}

	var mr MessageResponse
	err = json.Unmarshal(body, &mr)
	if err != nil {
		err := fmt.Errorf("Error parsing message response: %s", err)
		return "", err
	}

	if !mr.OK {
		return "", fmt.Errorf("Got !OK from slack message response")
	}

	return mr.Timestamp, err
}

func (s *SlackApp) replyToMessage(channel, message string, replyTo msg.Message) (string, error) {
	return s.replyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"])
}

func (s *SlackApp) react(channel, reaction string, message msg.Message) (string, error) {
	log.Printf("Reacting in %s: %s", channel, reaction)
	ref := slack.ItemRef{
		Channel:   channel,
		Timestamp: message.AdditionalData["RAW_SLACK_TIMESTAMP"],
	}
	err := s.api.AddReaction(reaction, ref)
	return "", err
}

func (s *SlackApp) edit(channel, newMessage, identifier string) (string, error) {
	log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage)
	nick := s.config.Get("Nick", "bot")
	_, ts, err := s.api.PostMessage(channel,
		slack.MsgOptionUsername(nick),
		slack.MsgOptionText(newMessage, false),
		slack.MsgOptionMeMessage(),
		slack.MsgOptionUpdate(identifier))
	return ts, err
}

func (s *SlackApp) GetEmojiList() map[string]string {
	return s.emoji
}

func (s *SlackApp) populateEmojiList() {
	if s.userToken == "NONE" {
		log.Println("Cannot get emoji list without slack.usertoken")
		return
	}
	api := slack.New(s.userToken, slack.OptionDebug(true))

	em, err := api.GetEmoji()
	if err != nil {
		log.Printf("Error retrieving emoji list from Slack: %s", err)
		return
	}

	s.emoji = em
}

// I think it's horseshit that I have to do this
func slackTStoTime(t string) time.Time {
	ts := strings.Split(t, ".")
	sec, _ := strconv.ParseInt(ts[0], 10, 64)
	nsec, _ := strconv.ParseInt(ts[1], 10, 64)
	return time.Unix(sec, nsec)
}

var urlDetector = regexp.MustCompile(`<(.+)://([^|^>]+).*>`)

// Convert a slackMessage to a msg.Message
func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message {
	text := html.UnescapeString(m.Text)

	text = fixText(s.getUser, text)

	isCmd, text := bot.IsCmd(s.config, text)

	isAction := m.SubType == "me_message"

	u, _ := s.getUser(m.User)
	if m.Username != "" {
		u = m.Username
	}

	tstamp := slackTStoTime(m.TimeStamp)

	return msg.Message{
		User: &user.User{
			ID:   m.User,
			Name: u,
		},
		Body:    text,
		Raw:     m.Text,
		Channel: m.Channel,
		Command: isCmd,
		Action:  isAction,
		Time:    tstamp,
		AdditionalData: map[string]string{
			"RAW_SLACK_TIMESTAMP": m.TimeStamp,
		},
	}
}

// Get username for Slack user ID
func (s *SlackApp) getUser(id string) (string, error) {
	if name, ok := s.users[id]; ok {
		return name, nil
	}

	log.Printf("User %s not already found, requesting info", id)
	u, err := s.api.GetUserInfo(id)
	if err != nil {
		return "UNKNOWN", err
	}
	s.users[id] = u.Name
	return s.users[id], nil
}

// Who gets usernames out of a channel
func (s *SlackApp) Who(id string) []string {
	if s.userToken == "NONE" {
		log.Println("Cannot get emoji list without slack.usertoken")
		return []string{s.config.Get("nick", "bot")}
	}
	api := slack.New(s.userToken, slack.OptionDebug(true))

	log.Println("Who is queried for ", id)
	// Not super sure this is the correct call
	params := &slack.GetUsersInConversationParameters{
		ChannelID: id,
		Limit:     50,
	}
	members, _, err := api.GetUsersInConversation(params)
	if err != nil {
		log.Println(err)
		return []string{s.config.Get("nick", "bot")}
	}

	ret := []string{}
	for _, m := range members {
		u, err := s.getUser(m)
		if err != nil {
			log.Printf("Couldn't get user %s: %s", m, err)
			continue
		}
		ret = append(ret, u)
	}
	return ret
}