package prompt

import (
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/chzyer/readline"
	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
	"go.buzzert.net/kordophone-mock/model"
	"go.buzzert.net/kordophone-mock/server"
)

type Prompt struct {
	rl     *readline.Instance
	server *server.Server
}

func (p *Prompt) conversationForGUID(guid string) (*model.Conversation, error) {
	if guid == "*" {
		// This means any conversation: return the first one (that's not the special Attachments convo)
		convo := p.server.SortedConversations()[0]
		if convo.DisplayName != nil && *convo.DisplayName == server.ATTACHMENT_CONVO_DISP_NAME {
			return &p.server.SortedConversations()[1], nil
		}

		return &convo, nil
	}

	return p.server.ConversationForGUID(guid)
}

func (p *Prompt) listConversations() {
	conversations := p.server.SortedConversations()
	for _, c := range conversations {
		fmt.Printf("%s %s \t %s ", c.Guid, c.GetDisplayName(), c.Date.Format("2006-01-02 15:04:05"))
		if c.UnreadCount > 0 {
			fmt.Printf("(%d unread)", c.UnreadCount)
		}

		fmt.Println()
	}
}

func (p *Prompt) listMessages(guid string) {
	conversation, err := p.conversationForGUID(guid)
	if err != nil {
		log.Err(err).Msgf("Error listing messages for conversation %s", guid)
		return
	}

	messages := p.server.MessagesForConversation(conversation)
	for _, m := range messages {
		var sender string
		if m.Sender == nil {
			sender = "(Me)"
		} else {
			sender = *m.Sender
		}

		fmt.Printf("%s %s From: %s\n", m.Guid, m.Date.Format("2006-01-02 15:04:05"), sender)
		fmt.Printf("\t %s\n", m.Text)
	}
}

func (p *Prompt) markConversation(guid string, read bool) {
	conversation, err := p.conversationForGUID(guid)
	if err != nil {
		log.Err(err).Msgf("Error marking conversation %s as read", guid)
		return
	}

	if read {
		conversation.UnreadCount = 0
	} else {
		conversation.UnreadCount = 1
	}
}

func (p *Prompt) receiveMessage(guid string, text string) {
	conversation, err := p.conversationForGUID(guid)
	if err != nil {
		log.Err(err).Msgf("Error receiving message for conversation %s", guid)
		return
	}

	message := model.Message{
		Guid:   uuid.New().String(),
		Sender: &conversation.Participants[0],
		Text:   text,
		Date:   model.Date(time.Now()),
	}

	p.server.ReceiveMessage(conversation, message)
}

func NewPrompt(server *server.Server) *Prompt {
	completer := readline.NewPrefixCompleter(
		readline.PcItem("ls"),
		readline.PcItem("mark",
			readline.PcItem("-r"),
		),
		readline.PcItem("help"),
		readline.PcItem("recv"),
		readline.PcItem("exit"),
	)

	rl, err := readline.NewEx(&readline.Config{
		Prompt:          "\033[31m»\033[0m ",
		HistoryFile:     "/tmp/readline.tmp",
		InterruptPrompt: "^C",
		EOFPrompt:       "exit",
		AutoComplete:    completer,

		HistorySearchFold: true,
	})

	if err != nil {
		panic(err)
	}

	return &Prompt{
		rl:     rl,
		server: server,
	}
}

func (p *Prompt) StartInteractive() error {
	for {
		line, err := p.rl.Readline()
		if err == readline.ErrInterrupt {
			if len(line) == 0 {
				break
			} else {
				continue
			}
		} else if err == io.EOF {
			break
		}

		line = strings.TrimSpace(line)

		switch {
		case strings.HasPrefix(line, "ls"): // List
			args := strings.Split(line, " ")

			if len(args) == 1 {
				p.listConversations()
			} else {
				p.listMessages(args[1])
			}

		case strings.HasPrefix(line, "mark"): // Mark
			args := strings.Split(line, " ")
			if len(args) < 2 {
				log.Info().Msgf("Usage: mark [-r] <guid>")
				continue
			}

			read := false
			if args[1] == "-r" {
				read = true
				args = args[1:]
			}

			p.markConversation(args[1], read)

		case strings.HasPrefix(line, "recv"): // Receive
			args := strings.Split(line, " ")
			if len(args) < 3 {
				log.Info().Msgf("Usage: recv <guid> <msg>")
				continue
			}

			body := strings.Join(args[2:], " ")

			// strip quotes
			if strings.HasPrefix(body, "\"") && strings.HasSuffix(body, "\"") {
				body = body[1 : len(body)-1]
			}

			p.receiveMessage(args[1], body)
		case line == "version": // Version
			fmt.Printf("Server version: %s\n", p.server.Version())
		case line == "help": // Help
			fmt.Println("Usage: <command> [args]")
			fmt.Println("Where <guid> is specified, '*' can be provided as a wildcard.")
			fmt.Println()
			fmt.Println("Commands:")
			fmt.Println("\tls                list conversations")
			fmt.Println("\tls <guid>         list messages for conversation")
			fmt.Println("\tmark [-r] <guid>  mark conversation as unread/[r]ead")
			fmt.Println("\trecv <guid> <msg> receive a message")
			fmt.Println("\tversion           print server version")
			fmt.Println("\thelp              show this help")
			fmt.Println("\texit              exits the program")

		case line == "exit": // Exit
			return nil

		default:
			if len(line) > 0 {
				fmt.Printf("Unknown command: %s\n", line)
			}
		}
	}

	return nil
}

func (p *Prompt) CleanAndRefreshForLogging() {
	p.rl.Clean()

	// xxx: Lazy hack to make sure this runs _after_ the log is written.
	go p.rl.Refresh()
}
