package message

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"local/truckstop/config"
	"local/truckstop/logtr"
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/matrix-org/gomatrix"
)

type Matrix struct {
	mock         bool
	homeserver   string
	username     string
	token        string
	room         string
	continuation string
}

func NewSOSMatrix() logtr.SOSer {
	conf := config.Get().Log.SOSMatrix
	return newMatrix(conf, "0")
}

func NewMatrix() Matrix {
	conf := config.Get().Message.Matrix
	return newMatrix(conf, GetMatrixContinuation())
}

func newMatrix(conf config.Matrix, cont string) Matrix {
	return Matrix{
		homeserver:   conf.Homeserver,
		username:     conf.Username,
		token:        conf.Token,
		room:         conf.Room,
		mock:         conf.Mock,
		continuation: cont,
	}
}

func (m Matrix) getclient() (*gomatrix.Client, error) {
	return gomatrix.NewClient(m.homeserver, m.username, m.token)
}

func (m Matrix) Continuation() string {
	return m.continuation
}

func (m *Matrix) Receive() ([]Message, error) {
	if m.mock {
		logtr.Infof("matrix.Receive()")
		messages := make([]Message, 0)
		for k := range config.Get().Clients {
			messages = append(messages, Message{Timestamp: time.Now(), Sender: k, Content: "!state nc"})
			if k == "bel" {
				messages = append(messages, Message{Timestamp: time.Now(), Sender: k, Content: "!help"})
			}
			if k == "broc" {
				messages = append(messages, Message{Timestamp: time.Now(), Sender: k, Content: "!available 2148-10-" + fmt.Sprint(time.Now().Unix()%28)})
			}
		}
		return messages, nil
	}
	clients := config.Get().Clients
	matrixIDs := map[string]struct{}{}
	for k := range clients {
		matrixIDs[clients[k].IDs.Matrix] = struct{}{}
	}
	if len(matrixIDs) == 0 {
		return nil, nil
	}
	c, err := m.getclient()
	if err != nil {
		return nil, err
	}
	messages := make([]Message, 0)
	result, err := c.Messages(m.room, "999999999999999999", m.Continuation(), 'b', 50)
	if err != nil {
		return nil, err
	}
	logtr.Verbosef("%s => {Start:%s End:%v};; %v, (%d)", m.Continuation(), result.Start, result.End, err, len(result.Chunk))
	m.continuation = result.End
	for _, event := range result.Chunk {
		logtr.Verbosef("matrix event: %+v", event)
		if _, ok := matrixIDs[event.Sender]; !ok {
			continue
		}
		switch event.Type {
		case "m.room.message":
			b, ok := event.Body()
			if ok {
				messages = append(messages, Message{Timestamp: time.Unix(0, event.Timestamp*int64(time.Millisecond)), Sender: event.Sender, Content: strings.TrimSpace(b)})
			}
		}
	}
	clientChange := regexp.MustCompile("@[a-z]+$")
	logtr.Verbosef("rewriting messages based on @abc")
	for i := range messages {
		if found := clientChange.FindString(messages[i].Content); found != "" {
			messages[i].Content = strings.TrimSpace(strings.ReplaceAll(messages[i].Content, found, ""))
			messages[i].Sender = found[1:]
		} else {
			for k, v := range config.Get().Clients {
				if v.IDs.Matrix == messages[i].Sender {
					messages[i].Sender = k
				}
			}
		}
		messages[i].Content = strings.TrimSpace(messages[i].Content)
	}
	logtr.Verbosef("rewriting messages based on ! CoMmAnD ...")
	for i := range messages {
		if strings.HasPrefix(messages[i].Content, "!") {
			messages[i].Content = "!" + strings.TrimSpace(messages[i].Content[1:])
			splits := strings.Split(messages[i].Content, " ")
			messages[i].Content = strings.ToLower(splits[0]) + " " + strings.Join(splits, " ")
		}
	}
	return messages, nil
}

func (m Matrix) Remove(id string) error {
	if m.mock {
		logtr.Infof("matrix.Remove(%s)", id)
		return nil
	}
	c, err := m.getclient()
	if err != nil {
		return err
	}
	_, err = c.RedactEvent(m.room, id, &gomatrix.ReqRedact{Reason: "stale"})
	return err
}

func (m Matrix) Update(id, text string) error {
	if m.mock {
		logtr.Infof("matrix.Update(%s)", text)
		return nil
	}
	c, err := m.getclient()
	if err != nil {
		return err
	}
	type MRelatesTo struct {
		EventID string `json:"event_id"`
		RelType string `json:"rel_type"`
	}
	type NewContent struct {
		Body    string `json:"body"`
		MsgType string `json:"msgtype"`
	}
	type RelatesToRoomMessage struct {
		Body        string     `json:"body"`
		MsgType     string     `json:"msgtype"`
		MRelatesTo  MRelatesTo `json:"m.relates_to"`
		MNewContent NewContent `json:"m.new_content"`
	}
	_, err = c.SendMessageEvent(m.room, "m.room.message", RelatesToRoomMessage{
		Body:    "",
		MsgType: "m.text",
		MNewContent: NewContent{
			Body:    text,
			MsgType: "m.text",
		},
		MRelatesTo: MRelatesTo{
			EventID: id,
			RelType: "m.replace",
		},
	})
	return err
}

func (m Matrix) Send(text string) error {
	_, err := m.SendTracked(text)
	return err
}

func (m Matrix) SendTracked(text string) (string, error) {
	if m.mock {
		logtr.Infof("matrix.SendTracked(%s)", text)
		return "", nil
	}
	c, err := m.getclient()
	if err != nil {
		return "", err
	}
	resp, err := c.SendText(m.room, text)
	if err != nil {
		return "", err
	}
	return resp.EventID, nil
}

func (m Matrix) SendImage(uri string) error {
	_, err := m.SendImageTracked(uri)
	return err
}

func (m Matrix) SendImageTracked(uri string) (string, error) {
	if m.mock {
		logtr.Infof("matrix.SendImage(%s)", uri)
		return "", nil
	}
	response, err := http.Get(uri)
	if err != nil {
		return "", err
	}
	if response.StatusCode != http.StatusOK {
		b, _ := ioutil.ReadAll(response.Body)
		response.Body.Close()
		return "", fmt.Errorf("failed to get %s: (%d) %s", uri, response.StatusCode, b)
	}
	b, err := ioutil.ReadAll(response.Body)
	response.Body.Close()
	if err != nil {
		return "", err
	}
	c, err := m.getclient()
	if err != nil {
		return "", err
	}
	mediaUpload, err := c.UploadToContentRepo(bytes.NewReader(b), "image/jpeg", int64(len(b)))
	if err != nil {
		return "", err
	}
	publicURI := mediaUpload.ContentURI
	resp, err := c.SendImage(m.room, "img", publicURI)
	logtr.Verbosef("sent image %s => %s: %+v", uri, publicURI, resp)
	return resp.EventID, err
}

func SetMatrixContinuation(continuation string) {
	db := config.Get().DB()
	db.Set(getMatrixContinuationKey(), []byte(continuation))
}

func GetMatrixContinuation() string {
	db := config.Get().DB()
	b, _ := db.Get(getMatrixContinuationKey())
	if b == nil {
		return "0"
	}
	return string(b)
}

func getMatrixContinuationKey() string {
	return "matrix_continuation"
}
