package peers

import (
	"context"
	"sync"

	"go.uber.org/atomic"

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

// InmemoryStorage is basic in-memory Storage implementation.
type InmemoryStorage struct {
	phones  map[string]Key
	data    map[Key]Value
	dataMux sync.Mutex // guards phones and data

	contactsHash atomic.Int64
}

var _ Storage = (*InmemoryStorage)(nil)

func (f *InmemoryStorage) initLocked() {
	if f.phones == nil {
		f.phones = map[string]Key{}
	}
	if f.data == nil {
		f.data = map[Key]Value{}
	}
}

// Save implements Storage.
func (f *InmemoryStorage) Save(ctx context.Context, key Key, value Value) error {
	f.dataMux.Lock()
	defer f.dataMux.Unlock()
	f.initLocked()

	f.data[key] = value
	return nil
}

// Find implements Storage.
func (f *InmemoryStorage) Find(ctx context.Context, key Key) (value Value, found bool, _ error) {
	f.dataMux.Lock()
	defer f.dataMux.Unlock()

	value, found = f.data[key]
	return value, found, nil
}

// SavePhone implements Storage.
func (f *InmemoryStorage) SavePhone(ctx context.Context, phone string, key Key) error {
	f.dataMux.Lock()
	defer f.dataMux.Unlock()
	f.initLocked()

	f.phones[phone] = key
	return nil
}

// FindPhone implements Storage.
func (f *InmemoryStorage) FindPhone(ctx context.Context, phone string) (key Key, value Value, found bool, err error) {
	f.dataMux.Lock()
	defer f.dataMux.Unlock()

	key, found = f.phones[phone]
	if !found {
		return Key{}, Value{}, false, nil
	}
	value, found = f.data[key]
	return key, value, found, nil
}

// GetContactsHash implements Storage.
func (f *InmemoryStorage) GetContactsHash(ctx context.Context) (int64, error) {
	v := f.contactsHash.Load()
	return v, nil
}

// SaveContactsHash implements Storage.
func (f *InmemoryStorage) SaveContactsHash(ctx context.Context, hash int64) error {
	f.contactsHash.Store(hash)
	return nil
}

// InmemoryCache is basic in-memory Cache implementation.
type InmemoryCache struct {
	users    map[int64]*tg.User
	usersMux sync.Mutex

	usersFull    map[int64]*tg.UserFull
	usersFullMux sync.Mutex

	chats    map[int64]*tg.Chat
	chatsMux sync.Mutex

	chatsFull    map[int64]*tg.ChatFull
	chatsFullMux sync.Mutex

	channels    map[int64]*tg.Channel
	channelsMux sync.Mutex

	channelsFull    map[int64]*tg.ChannelFull
	channelsFullMux sync.Mutex
}

// SaveUsers implements Cache.
func (f *InmemoryCache) SaveUsers(ctx context.Context, users ...*tg.User) error {
	f.usersMux.Lock()
	defer f.usersMux.Unlock()
	if f.channelsFull == nil {
		f.users = map[int64]*tg.User{}
	}

	for _, u := range users {
		f.users[u.GetID()] = u
	}

	return nil
}

// SaveUserFulls implements Cache.
func (f *InmemoryCache) SaveUserFulls(ctx context.Context, users ...*tg.UserFull) error {
	f.usersFullMux.Lock()
	defer f.usersFullMux.Unlock()
	if f.channelsFull == nil {
		f.usersFull = map[int64]*tg.UserFull{}
	}

	for _, u := range users {
		f.usersFull[u.GetID()] = u
	}

	return nil
}

// FindUser implements Cache.
func (f *InmemoryCache) FindUser(ctx context.Context, id int64) (*tg.User, bool, error) {
	f.usersMux.Lock()
	defer f.usersMux.Unlock()

	u, ok := f.users[id]
	return u, ok, nil
}

// FindUserFull implements Cache.
func (f *InmemoryCache) FindUserFull(ctx context.Context, id int64) (*tg.UserFull, bool, error) {
	f.usersFullMux.Lock()
	defer f.usersFullMux.Unlock()

	u, ok := f.usersFull[id]
	return u, ok, nil
}

// SaveChats implements Cache.
func (f *InmemoryCache) SaveChats(ctx context.Context, chats ...*tg.Chat) error {
	f.chatsMux.Lock()
	defer f.chatsMux.Unlock()
	if f.channelsFull == nil {
		f.chats = map[int64]*tg.Chat{}
	}

	for _, c := range chats {
		f.chats[c.GetID()] = c
	}

	return nil
}

// SaveChatFulls implements Cache.
func (f *InmemoryCache) SaveChatFulls(ctx context.Context, chats ...*tg.ChatFull) error {
	f.chatsFullMux.Lock()
	defer f.chatsFullMux.Unlock()
	if f.channelsFull == nil {
		f.chatsFull = map[int64]*tg.ChatFull{}
	}

	for _, c := range chats {
		f.chatsFull[c.GetID()] = c
	}

	return nil
}

// FindChat implements Cache.
func (f *InmemoryCache) FindChat(ctx context.Context, id int64) (*tg.Chat, bool, error) {
	f.chatsMux.Lock()
	defer f.chatsMux.Unlock()

	c, ok := f.chats[id]
	return c, ok, nil
}

// FindChatFull implements Cache.
func (f *InmemoryCache) FindChatFull(ctx context.Context, id int64) (*tg.ChatFull, bool, error) {
	f.chatsFullMux.Lock()
	defer f.chatsFullMux.Unlock()

	c, ok := f.chatsFull[id]
	return c, ok, nil
}

// SaveChannels implements Cache.
func (f *InmemoryCache) SaveChannels(ctx context.Context, channels ...*tg.Channel) error {
	f.channelsMux.Lock()
	defer f.channelsMux.Unlock()
	if f.channelsFull == nil {
		f.channels = map[int64]*tg.Channel{}
	}

	for _, c := range channels {
		f.channels[c.GetID()] = c
	}

	return nil
}

// SaveChannelFulls implements Cache.
func (f *InmemoryCache) SaveChannelFulls(ctx context.Context, channels ...*tg.ChannelFull) error {
	f.channelsFullMux.Lock()
	defer f.channelsFullMux.Unlock()
	if f.channelsFull == nil {
		f.channelsFull = map[int64]*tg.ChannelFull{}
	}

	for _, c := range channels {
		f.channelsFull[c.GetID()] = c
	}

	return nil
}

// FindChannel implements Cache.
func (f *InmemoryCache) FindChannel(ctx context.Context, id int64) (*tg.Channel, bool, error) {
	f.channelsMux.Lock()
	defer f.channelsMux.Unlock()

	c, ok := f.channels[id]
	return c, ok, nil
}

// FindChannelFull implements Cache.
func (f *InmemoryCache) FindChannelFull(ctx context.Context, id int64) (*tg.ChannelFull, bool, error) {
	f.channelsFullMux.Lock()
	defer f.channelsFullMux.Unlock()

	c, ok := f.channelsFull[id]
	return c, ok, nil
}
