package waitlist

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/flowy-live/llink/internal/utils/flog"

	"github.com/jackc/pgx/v5/pgxpool"

	pbaero "github.com/flowy-live/llink/genproto/aero"
	"github.com/flowy-live/llink/internal/utils"
)

type WaitlistEntry struct {
	Id        int               `db:"id"`
	Email     string            `db:"email"`
	Metadata  map[string]string `db:"metadata"`
	CreatedAt time.Time         `db:"created_at"`
	InvitedAt *time.Time        `db:"invited_at"`
}

var (
	AlreadyInWaitlistError = errors.New("already in the waitlist")
	EntryNotFoundError     = errors.New("not found")
)

type GetWaitlistFilter string

const (
	GetWaitlistFilterAll           GetWaitlistFilter = "all"
	GetWaitlistFilterInvitedOnly   GetWaitlistFilter = "invited-only"
	GetWaitlistFilterUninvitedOnly GetWaitlistFilter = "uninvited-only"
)

type Service interface {
	// AddToWaitlist returns AlreadyInWaitlistError if the email is already present.
	AddToWaitlist(ctx context.Context, email string, metadata map[string]string) error
	GetWaitlist(ctx context.Context, filter GetWaitlistFilter) ([]*WaitlistEntry, error)
	// GetWaitlistEntryByEmail returns EntryNotFoundError if missing.
	GetWaitlistEntryByEmail(ctx context.Context, email string) (*WaitlistEntry, error)
	MarkWaitlistEntryInvited(ctx context.Context, email string) error
}

type serviceImpl struct {
	repo    repository
	aeroSvc pbaero.PrimaryClient
}

func NewService(pool *pgxpool.Pool, aeroSvc pbaero.PrimaryClient) Service {
	return &serviceImpl{
		repo:    newRepository(pool),
		aeroSvc: aeroSvc,
	}
}

func (s *serviceImpl) AddToWaitlist(ctx context.Context, email string, metadata map[string]string) error {
	normalizedEmail, err := utils.NormalizeEmail(email)
	if err != nil {
		return errors.New("invalid email")
	}

	if metadata == nil {
		metadata = map[string]string{}
	}

	_, err = s.repo.create(ctx, normalizedEmail, metadata)
	if err != nil {
		if errors.Is(err, errAlreadyExists) {
			return AlreadyInWaitlistError
		}
		return err
	}

	message := fmt.Sprintf("New human added to the llink waitlist: %s", email)
	_, err = s.aeroSvc.ShootEmail(context.Background(), &pbaero.ShootEmailRequest{
		ToEmails: []string{"team@flowylabs.ai"},
		Subject:  "Flowy Llink - New Waitlist Subscriber",
		TemplateData: &pbaero.ShootEmailRequest_GenericFlowyAdminAlertData{
			GenericFlowyAdminAlertData: &pbaero.GenericFlowyAdminAlertData{
				Message: message,
			},
		},
	})
	if err != nil {
		flog.Error("failed to add to waitlist", "error", err, "email", email)
	}

	return nil
}

func (s *serviceImpl) GetWaitlist(ctx context.Context, filter GetWaitlistFilter) ([]*WaitlistEntry, error) {
	return s.repo.getAll(ctx, filter)
}

func (s *serviceImpl) GetWaitlistEntryByEmail(ctx context.Context, email string) (*WaitlistEntry, error) {
	normalizedEmail, err := utils.NormalizeEmail(email)
	if err != nil {
		return nil, errors.New("invalid email")
	}

	entry, err := s.repo.getByEmail(ctx, normalizedEmail)
	if err != nil {
		if errors.Is(err, errNotFound) {
			return nil, EntryNotFoundError
		}
		return nil, err
	}

	return entry, nil
}

func (s *serviceImpl) MarkWaitlistEntryInvited(ctx context.Context, email string) error {
	normalizedEmail, err := utils.NormalizeEmail(email)
	if err != nil {
		return errors.New("invalid email")
	}

	err = s.repo.markInvited(ctx, normalizedEmail)
	if err != nil {
		if errors.Is(err, errNotFound) {
			return EntryNotFoundError
		}
		return err
	}

	return nil
}
