package memory

import (
	"context"
	"sync"
	"time"

	"source.toby3d.me/toby3d/auth/internal/domain"
	"source.toby3d.me/toby3d/auth/internal/ticket"
)

type (
	Ticket struct {
		CreatedAt time.Time
		domain.Ticket
	}

	memoryTicketRepository struct {
		config  domain.Config
		mutex   *sync.RWMutex
		tickets map[string]Ticket
	}
)

func NewMemoryTicketRepository(config domain.Config) ticket.Repository {
	return &memoryTicketRepository{
		config:  config,
		mutex:   new(sync.RWMutex),
		tickets: make(map[string]Ticket),
	}
}

func (repo *memoryTicketRepository) Create(_ context.Context, t domain.Ticket) error {
	repo.mutex.Lock()
	defer repo.mutex.Unlock()

	repo.tickets[t.Ticket] = Ticket{
		CreatedAt: time.Now().UTC(),
		Ticket:    t,
	}

	return nil
}

func (repo *memoryTicketRepository) GetAndDelete(_ context.Context, t string) (*domain.Ticket, error) {
	repo.mutex.RLock()

	out, ok := repo.tickets[t]
	if !ok {
		repo.mutex.RUnlock()

		return nil, ticket.ErrNotExist
	}

	repo.mutex.RUnlock()
	repo.mutex.Lock()
	delete(repo.tickets, t)
	repo.mutex.Unlock()

	return &out.Ticket, nil
}

func (repo *memoryTicketRepository) GC() {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for ts := range ticker.C {
		ts := ts.UTC()

		repo.mutex.RLock()

		for _, t := range repo.tickets {
			if t.CreatedAt.Add(repo.config.Code.Expiry).After(ts) {
				continue
			}

			repo.mutex.RUnlock()
			repo.mutex.Lock()
			delete(repo.tickets, t.Ticket.Ticket)
			repo.mutex.Unlock()
			repo.mutex.RLock()
		}

		repo.mutex.RUnlock()
	}
}
