// Package e2e contains end-to-end updates processing test.
package e2e

import (
	"context"
	"sync"

	"github.com/go-faster/errors"

	"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)

// Server for testing gaps.
type server struct {
	date     int
	peers    *peerDatabase
	messages *messageDatabase

	mux sync.Mutex
}

// NewServer creates new test server.
func newServer() *server {
	return &server{
		date: 1,
		peers: &peerDatabase{
			users:    make(map[int64]*tg.User),
			chats:    make(map[int64]*tg.Chat),
			channels: make(map[int64]*tg.Channel),
		},
		messages: &messageDatabase{
			channels: make(map[int64][]tg.MessageClass),
		},
	}
}

func (s *server) ChannelsGetParticipant(ctx context.Context, request *tg.ChannelsGetParticipantRequest) (*tg.ChannelsChannelParticipant, error) {
	return &tg.ChannelsChannelParticipant{
		Participant: &tg.ChannelParticipantSelf{},
	}, nil
}

// UpdatesGetState returns current remote state.
func (s *server) UpdatesGetState(ctx context.Context) (*tg.UpdatesState, error) {
	s.mux.Lock()
	defer s.mux.Unlock()

	return &tg.UpdatesState{
		Pts:  len(s.messages.common),
		Qts:  len(s.messages.secret),
		Date: s.date,
		Seq:  0,
	}, nil
}

// UpdatesGetDifference returns difference between local and remote states.
func (s *server) UpdatesGetDifference(ctx context.Context, request *tg.UpdatesGetDifferenceRequest) (tg.UpdatesDifferenceClass, error) {
	s.mux.Lock()
	defer s.mux.Unlock()

	ents := NewEntities()
	var common []tg.MessageClass
	for i := request.Pts + 1; i <= len(s.messages.common); i++ {
		common = append(common, s.messages.common[i-1])
		s.fillMessageEnts(s.messages.common[i-1], ents)
	}

	var secret []tg.EncryptedMessageClass
	for i := request.Qts + 1; i <= len(s.messages.secret); i++ {
		secret = append(secret, s.messages.secret[i-1])
	}

	var others []tg.UpdateClass
	for _, msgs := range s.messages.channels {
		for i, msg := range msgs {
			if msg.(*tg.Message).Date > request.Date {
				others = append(others, &tg.UpdateNewChannelMessage{
					Message:  msg,
					Pts:      i + 1,
					PtsCount: 1,
				})
				s.fillMessageEnts(msg, ents)
			}
		}
	}

	if len(common) == 0 && len(secret) == 0 && len(others) == 0 {
		return &tg.UpdatesDifferenceEmpty{
			Date: s.date,
			Seq:  0,
		}, nil
	}

	return &tg.UpdatesDifference{
		NewMessages:          common,
		NewEncryptedMessages: secret,
		OtherUpdates:         others,
		Users:                ents.AsUsers(),
		Chats:                ents.AsChats(),
		State: tg.UpdatesState{
			Pts:  len(s.messages.common),
			Qts:  len(s.messages.secret),
			Date: s.date,
			Seq:  0,
		},
	}, nil
}

// UpdatesGetChannelDifference returns difference between local and remote channel states.
func (s *server) UpdatesGetChannelDifference(
	ctx context.Context, request *tg.UpdatesGetChannelDifferenceRequest,
) (tg.UpdatesChannelDifferenceClass, error) {
	s.mux.Lock()
	defer s.mux.Unlock()

	channel, ok := request.Channel.(*tg.InputChannel)
	if !ok {
		return nil, errors.Errorf("bad InputChannelClass type: %T", request.Channel)
	}

	if peer, ok := s.peers.channels[channel.ChannelID]; true {
		if !ok {
			return nil, errors.Errorf("channel %d not found", channel.ChannelID)
		}

		if peer.AccessHash != channel.AccessHash {
			return nil, errors.New("invalid access hash")
		}
	}

	var (
		channelMsgs = s.messages.channels[channel.ChannelID]
		ents        = NewEntities()
		prepared    []tg.MessageClass
	)

	for i := request.Pts + 1; i <= len(channelMsgs); i++ {
		prepared = append(prepared, channelMsgs[i-1])
		s.fillMessageEnts(channelMsgs[i-1], ents)
	}

	if len(prepared) == 0 {
		return &tg.UpdatesChannelDifferenceEmpty{
			Pts:   len(channelMsgs),
			Final: true,
		}, nil
	}

	return &tg.UpdatesChannelDifference{
		NewMessages: prepared,
		Users:       ents.AsUsers(),
		Chats:       ents.AsChats(),
		Pts:         len(channelMsgs),
		Final:       true,
	}, nil
}

func (s *server) fillMessageEnts(msg tg.MessageClass, ents *Entities) {
	switch peer := msg.(*tg.Message).PeerID.(type) {
	case *tg.PeerUser:
		user, ok := s.peers.users[peer.UserID]
		if !ok {
			panic("bad user")
		}

		ents.Users[user.ID] = user
	case *tg.PeerChat:
		chat, ok := s.peers.chats[peer.ChatID]
		if !ok {
			panic("bad chat")
		}

		ents.Chats[chat.ID] = chat
	case *tg.PeerChannel:
		channel, ok := s.peers.channels[peer.ChannelID]
		if !ok {
			panic("bad channel")
		}

		ents.Channels[channel.ID] = channel
	default:
		panic("unexpected peer type")
	}

	peerUser, ok := msg.(*tg.Message).FromID.(*tg.PeerUser)
	if !ok {
		panic("bad fromID")
	}

	user, ok := s.peers.users[peerUser.UserID]
	if !ok {
		panic("bad user")
	}

	ents.Users[user.ID] = user
}
