package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/gempir/go-twitch-irc/v4"
	"github.com/google/uuid"
)

// banphraseResponse is the data we receive back from
// the banphrase API
type banphraseResponse struct {
	Banned        bool          `json:"banned"`
	InputMessage  string        `json:"input_message"`
	BanphraseData banphraseData `json:"banphrase_data"`
}

// banphraseData contains details about why a message
// was banphrased.
type banphraseData struct {
	Id        int    `json:"id"`
	Name      string `json:"name"`
	Phrase    string `json:"phrase"`
	Length    int    `json:"length"`
	Permanent bool   `json:"permanent"`
}

var (
	banPhraseUrl = "https://pajlada.pajbot.com/api/v1/banphrases/test"
)

// CheckMessage checks a given message against the banphrase api.
// returns false, "okay" if a message is allowed
// returns true and a string with the reason if it was banned.
// More information:
// https://gist.github.com/pajlada/57464e519ba8d195a97ddcd0755f9715
func (app *application) checkMessage(text string) (bool, string) {
	// {"message": "AHAHAHAHA LUL"}
	reqBody, err := json.Marshal(map[string]string{
		"message": text,
	})
	if err != nil {
		app.Log.Error(err)
		return true, "could not check banphrase api"
	}

	resp, err := http.Post(banPhraseUrl, "application/json", bytes.NewBuffer(reqBody))
	if err != nil {
		app.Log.Error(err)
		return true, "could not check banphrase api"
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		app.Log.Error(err)
	}

	var responseObject banphraseResponse
	if err := json.Unmarshal(body, &responseObject); err != nil {
		app.Log.Error(err)
		return true, "could not check banphrase api"
	}

	// Bad Message
	//
	// {"phrase": "No gyazo allowed"}
	reason := responseObject.BanphraseData.Name
	if responseObject.Banned {
		return true, fmt.Sprint(reason)
	} else if !responseObject.Banned {
		// Good message
		return false, "okay"
	}

	// Couldn't contact api so assume it was a bad message
	return true, "Banphrase API couldn't be reached monkaS"
}

// Send is used to send twitch replies and contains the necessary
// safeguards and logic for that.
func (app *application) SendNoContext(target, message string) {
	// Message we are trying to send is empty.
	if len(message) == 0 {
		return
	}

	identifier := uuid.NewString()
	go app.Models.SentMessagesLogs.Insert(target, message, "unavailable", "unavailable", "unavailable", "unavailable", identifier, "unavailable")

	// Since messages starting with `.` or `/` are used for special actions
	// (ban, whisper, timeout) and so on, we place an emote infront of it so
	// the actions wouldn't execute. `!` and `$` are common bot prefixes so we
	// don't allow them either.
	if message[0] == '.' || message[0] == '/' || message[0] == '!' || message[0] == '$' {
		message = ":tf: " + message
	}

	// check the message for bad words before we say it
	messageBanned, banReason := app.checkMessage(message)
	if !messageBanned {
		// In case the message we are trying to send is longer than the
		// maximum allowed message length on twitch we split the message in two parts.
		// Twitch has a maximum length for messages of 510 characters so to be safe
		// we split and check at 500 characters.
		// https://discuss.dev.twitch.tv/t/missing-client-side-message-length-check/21316
		// TODO: Make it so it splits at a space instead and not in the middle of a word.
		if len(message) > 500 {
			firstMessage := message[0:499]
			secondMessage := message[499:]

			app.TwitchClient.Say(target, firstMessage)
			app.TwitchClient.Say(target, secondMessage)

			return
		} else {
			// Message was fine.
			go app.TwitchClient.Say(target, message)
			return
		}
	} else {
		// Bad message, replace message and log it.
		app.TwitchClient.Say(target, "[BANPHRASED] monkaS")
		app.Log.Infow("banned message detected",
			"target channel", target,
			"message", message,
			"ban reason", banReason,
		)

		return
	}
}

// Send is used to send twitch replies and contains the necessary
// safeguards and logic for that.
func (app *application) Send(target, message string, msgContext twitch.PrivateMessage) {
	// Message we are trying to send is empty.
	if len(message) == 0 {
		return
	}

	commandName := strings.ToLower(strings.SplitN(msgContext.Message, " ", 3)[0][2:])
	identifier := uuid.NewString()
	go app.Models.SentMessagesLogs.Insert(target, message, commandName, msgContext.User.Name, msgContext.User.ID, msgContext.Message, identifier, msgContext.Raw)

	// Since messages starting with `.` or `/` are used for special actions
	// (ban, whisper, timeout) and so on, we place an emote infront of it so
	// the actions wouldn't execute. `!` and `$` are common bot prefixes so we
	// don't allow them either.
	if message[0] == '.' || message[0] == '/' || message[0] == '!' || message[0] == '$' {
		message = ":tf: " + message
	}

	// check the message for bad words before we say it
	messageBanned, banReason := app.checkMessage(message)
	if !messageBanned {
		// In case the message we are trying to send is longer than the
		// maximum allowed message length on twitch we split the message in two parts.
		// Twitch has a maximum length for messages of 510 characters so to be safe
		// we split and check at 500 characters.
		// https://discuss.dev.twitch.tv/t/missing-client-side-message-length-check/21316
		// TODO: Make it so it splits at a space instead and not in the middle of a word.
		if len(message) > 500 {
			firstMessage := message[0:499]
			secondMessage := message[499:]

			app.TwitchClient.Say(target, firstMessage)
			app.TwitchClient.Say(target, secondMessage)

			return
		} else {
			// Message was fine.
			go app.TwitchClient.Say(target, message)
			return
		}
	} else {
		// Bad message, replace message and log it.
		app.TwitchClient.Say(target, "[BANPHRASED] monkaS")
		app.Log.Infow("banned message detected",
			"target channel", target,
			"message", message,
			"ban reason", banReason,
		)

		return
	}
}

// Send is used to send twitch replies and contains the necessary
// safeguards and logic for that.
func (app *application) SendNoBanphrase(target, message string) {
	// Message we are trying to send is empty.
	if len(message) == 0 {
		return
	}

	identifier := uuid.NewString()
	go app.Models.SentMessagesLogs.Insert(target, message, "unavailable", "unavailable", "unavailable", "unavailable", identifier, "unavailable")

	// Since messages starting with `.` or `/` are used for special actions
	// (ban, whisper, timeout) and so on, we place an emote infront of it so
	// the actions wouldn't execute. `!` and `$` are common bot prefixes so we
	// don't allow them either.
	if message[0] == '.' || message[0] == '/' || message[0] == '!' || message[0] == '$' {
		message = ":tf: " + message
	}

	// check the message for bad words before we say it
	// Message was fine.
	go app.TwitchClient.Say(target, message)
}

// SendNoLimit does not check for the maximum message size.
// Used in sending commands from the database since the command has to have
// been gotten in there somehow. So it fits. Still checks for banphrases.
func (app *application) SendNoLimit(target, message string) {
	// Message we are trying to send is empty.
	if len(message) == 0 {
		return
	}

	// Since messages starting with `.` or `/` are used for special actions
	// (ban, whisper, timeout) and so on, we place an emote infront of it so
	// the actions wouldn't execute. `!` and `$` are common bot prefixes so we
	// don't allow them either.
	if message[0] == '.' || message[0] == '/' || message[0] == '!' || message[0] == '$' {
		message = ":tf: " + message
	}

	// check the message for bad words before we say it
	messageBanned, banReason := app.checkMessage(message)
	if messageBanned {
		// Bad message, replace message and log it.
		go app.TwitchClient.Say(target, "[BANPHRASED] monkaS")
		app.Log.Infow("banned message detected",
			"target channel", target,
			"message", message,
			"ban reason", banReason,
		)

		return
	} else {
		// In case the message we are trying to send is longer than the
		// maximum allowed message length on twitch we split the message in two parts.
		// Twitch has a maximum length for messages of 510 characters so to be safe
		// we split and check at 500 characters.
		// https://discuss.dev.twitch.tv/t/missing-client-side-message-length-check/21316
		// TODO: Make it so it splits at a space instead and not in the middle of a word.
		// Message was fine.
		identifier := uuid.NewString()
		go app.Models.SentMessagesLogs.Insert(target, message, "unavailable", "unavailable", "unavailable", "unavailable", identifier, "unavailable")
		go app.TwitchClient.Say(target, message)
		return
	}
}