package shibshib

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"

	authv1 "github.com/harmony-development/shibshib/gen/auth/v1"
	chatv1 "github.com/harmony-development/shibshib/gen/chat/v1"
	profilev1 "github.com/harmony-development/shibshib/gen/profile/v1"
)

type Client struct {
	ChatKit    chatv1.HTTPChatServiceClient
	AuthKit    authv1.HTTPAuthServiceClient
	ProfileKit profilev1.HTTPProfileServiceClient

	ErrorHandler func(error)

	UserID uint64

	incomingEvents <-chan *chatv1.StreamEventsResponse
	outgoingEvents chan<- *chatv1.StreamEventsRequest

	subscribedGuilds []uint64
	onceHandlers     []func(*LocatedMessage)

	events       chan *LocatedMessage
	homeserver   string
	sessionToken string

	streaming bool

	mtx *sync.Mutex
}

var ErrEndOfStream = errors.New("end of stream")

func (c *Client) init(h string, wsp, wsph string) {
	c.events = make(chan *LocatedMessage)
	c.mtx = new(sync.Mutex)
	c.ErrorHandler = func(e error) {
		panic(e)
	}
	c.homeserver = h
	c.ChatKit = chatv1.HTTPChatServiceClient{*http.DefaultClient, h, wsp, wsph, http.Header{}}
	c.AuthKit = authv1.HTTPAuthServiceClient{*http.DefaultClient, h, wsp, wsph, http.Header{}}
	c.ProfileKit = profilev1.HTTPProfileServiceClient{*http.DefaultClient, h, wsp, wsph, http.Header{}}
}

func (c *Client) authed(token string, userID uint64) {
	c.sessionToken = token
	c.ChatKit.Header.Add("Authorization", token)
	c.AuthKit.Header.Add("Authorization", token)
	c.ProfileKit.Header.Add("Authorization", token)
	c.UserID = userID
}

func NewClient(homeserver, token string, userid uint64) (ret *Client, err error) {
	url, err := url.Parse(homeserver)
	if err != nil {
		return nil, err
	}
	it := "wss"
	if url.Scheme == "http" {
		it = "ws"
	}
	ret = &Client{}
	ret.homeserver = homeserver
	ret.init(homeserver, it, url.Host)
	ret.authed(token, userid)

	err = ret.StreamEvents()
	if err != nil {
		ret = nil
		return
	}

	return
}

func (c *Client) StreamEvents() (err error) {
	c.mtx.Lock()
	defer c.mtx.Unlock()

	if c.streaming {
		return
	}

	it := make(chan *chatv1.StreamEventsRequest)
	c.outgoingEvents = it
	c.incomingEvents, err = c.ChatKit.StreamEvents(it)
	if err != nil {
		err = fmt.Errorf("StreamEvents: failed to open stream: %w", err)
		return
	}

	c.streaming = true

	go func() {
		for ev := range c.incomingEvents {
			chat, ok := ev.Event.(*chatv1.StreamEventsResponse_Chat)
			if !ok {
				continue
			}

			msg, ok := chat.Chat.Event.(*chatv1.StreamEvent_SentMessage)
			if !ok {
				continue
			}

			imsg := &LocatedMessage{
				GuildID:   msg.SentMessage.GuildId,
				ChannelID: msg.SentMessage.ChannelId,
				MessageWithId: chatv1.MessageWithId{
					MessageId: msg.SentMessage.MessageId,
					Message:   msg.SentMessage.Message,
				},
			}

			for _, h := range c.onceHandlers {
				h(imsg)
			}
			c.onceHandlers = make([]func(*LocatedMessage), 0)
			c.events <- imsg
		}

		c.mtx.Lock()
		defer c.mtx.Unlock()

		c.streaming = false
		c.ErrorHandler(ErrEndOfStream)
	}()

	return nil
}

func (c *Client) SubscribeToGuild(community uint64) {
	for _, g := range c.subscribedGuilds {
		if g == community {
			return
		}
	}
	c.outgoingEvents <- &chatv1.StreamEventsRequest{
		Request: &chatv1.StreamEventsRequest_SubscribeToGuild_{
			SubscribeToGuild: &chatv1.StreamEventsRequest_SubscribeToGuild{
				GuildId: community,
			},
		},
	}
	c.subscribedGuilds = append(c.subscribedGuilds, community)
}

func (c *Client) SubscribedGuilds() []uint64 {
	return c.subscribedGuilds
}

func (c *Client) SendMessage(msg *chatv1.SendMessageRequest) (*chatv1.SendMessageResponse, error) {
	return c.ChatKit.SendMessage(msg)
}

func (c *Client) TransformHMCURL(hmc string) string {
	if !strings.HasPrefix(hmc, "hmc://") {
		return fmt.Sprintf("%s/_harmony/media/download/%s", c.homeserver, hmc)
	}

	trimmed := strings.TrimPrefix(hmc, "hmc://")
	split := strings.Split(trimmed, "/")
	if len(split) != 2 {
		return fmt.Sprintf("malformed URL: %s", hmc)
	}

	return fmt.Sprintf("https://%s/_harmony/media/download/%s", split[0], split[1])
}

func (c *Client) UsernameFor(m *chatv1.Message) string {
	if m.Overrides != nil {
		return m.Overrides.GetUsername()
	}

	resp, err := c.ProfileKit.GetProfile(&profilev1.GetProfileRequest{
		UserId: m.AuthorId,
	})
	if err != nil {
		return strconv.FormatUint(m.AuthorId, 10)
	}

	return resp.Profile.UserName
}

func (c *Client) AvatarFor(m *chatv1.Message) string {
	if m.Overrides != nil {
		return m.Overrides.GetAvatar()
	}

	resp, err := c.ProfileKit.GetProfile(&profilev1.GetProfileRequest{
		UserId: m.AuthorId,
	})
	if err != nil {
		return ""
	}

	return c.TransformHMCURL(resp.Profile.GetUserAvatar())
}

func (c *Client) EventsStream() <-chan *LocatedMessage {
	return c.events
}

func (c *Client) HandleOnce(f func(*LocatedMessage)) {
	c.onceHandlers = append(c.onceHandlers, f)
}