package network

import (
	"context"
	"errors"
	"fmt"

	"github.com/flowy-live/llink/internal/utils"
	"github.com/jackc/pgx/v5/pgxpool"
)

type Reader interface {
	// GetByID returns ErrNotFound if network doesn't exist.
	GetByID(ctx context.Context, id string) (*Network, error)
	// ListForHuman returns ErrInvalidHumanId if humanId is empty.
	ListForHuman(ctx context.Context, humanId string) ([]*Network, error)
	// IsMember returns ErrInvalidHumanId if humanId is empty.
	IsMember(ctx context.Context, networkID, humanId string) (bool, error)
	// ListMembers returns an empty slice if the network doesn't exist.
	ListMembers(ctx context.Context, networkID string) ([]string, error)
	ListAll(ctx context.Context) ([]*Network, error)
	// ListAllMemberships returns humanId -> networkIds for every human with at
	// least one membership. Humans with zero memberships are absent from the map.
	ListAllMemberships(ctx context.Context) (map[string][]string, error)

	CountSeats(ctx context.Context, networkID string) (int, error)
	// ListInvitationsForEmail returns ErrInvalidEmail if normalization fails.
	ListInvitationsForEmail(ctx context.Context, email string) ([]*Invitation, error)
	ListInvitationsForNetwork(ctx context.Context, networkID string) ([]*Invitation, error)
}

type readerImpl struct {
	pool *pgxpool.Pool
	repo repository
}

// newReader exposes the concrete type so the service can embed it without
// hiding pool/repo behind the Reader interface.
func newReader(pool *pgxpool.Pool) *readerImpl {
	return &readerImpl{
		pool: pool,
		repo: newRepository(pool),
	}
}

func NewReader(pool *pgxpool.Pool) Reader {
	return newReader(pool)
}

func (r *readerImpl) GetByID(ctx context.Context, id string) (*Network, error) {
	n, err := r.repo.getByID(ctx, id)
	if errors.Is(err, errNotFound) {
		return nil, ErrNotFound
	}
	return n, err
}

func (r *readerImpl) ListForHuman(ctx context.Context, humanId string) ([]*Network, error) {
	if humanId == "" {
		return nil, ErrInvalidHumanId
	}
	return r.repo.getNetworksForHuman(ctx, humanId)
}

func (r *readerImpl) IsMember(ctx context.Context, networkID, humanId string) (bool, error) {
	if humanId == "" {
		return false, ErrInvalidHumanId
	}
	return r.repo.isMember(ctx, networkID, humanId)
}

func (r *readerImpl) ListMembers(ctx context.Context, networkID string) ([]string, error) {
	// Admin is guaranteed to be in network_members: Create() calls AddMembers
	// for the admin, and RemoveMemberFromNetwork rejects admin removal.
	return r.repo.getMemberHumanIds(ctx, networkID)
}

func (r *readerImpl) ListAll(ctx context.Context) ([]*Network, error) {
	return r.repo.listAll(ctx)
}

func (r *readerImpl) ListAllMemberships(ctx context.Context) (map[string][]string, error) {
	return r.repo.listAllMemberships(ctx)
}

func (r *readerImpl) CountSeats(ctx context.Context, networkID string) (int, error) {
	return r.repo.countSeats(ctx, r.pool, networkID)
}

func (r *readerImpl) ListInvitationsForEmail(ctx context.Context, email string) ([]*Invitation, error) {
	normalized, err := utils.NormalizeEmail(email)
	if err != nil {
		return nil, fmt.Errorf("%w: %w", ErrInvalidEmail, err)
	}
	return r.repo.getInvitationsByEmail(ctx, normalized)
}

func (r *readerImpl) ListInvitationsForNetwork(ctx context.Context, networkID string) ([]*Invitation, error) {
	return r.repo.getInvitationsByNetwork(ctx, networkID)
}
