package subscription

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"
)

type Store struct {
	db *sqlx.DB
}

func NewStore(db *sqlx.DB) *Store {
	return &Store{db: db}
}

func (s *Store) Create(ctx context.Context, sub *Subscription) (*Subscription, error) {
	sub.ID = uuid.New().String()

	const q = `
		INSERT INTO subscription (id, channel_id, channel_name, mode, filter, created_by)
		VALUES (?, ?, ?, ?, ?, ?)
	`
	_, err := s.db.ExecContext(ctx, q, sub.ID, sub.ChannelID, sub.ChannelName, sub.Mode, sub.Filter, sub.CreatedBy)
	if err != nil {
		return nil, fmt.Errorf("insert subscription: %w", err)
	}
	return sub, nil
}

func (s *Store) GetByID(ctx context.Context, id string) (*Subscription, error) {
	const q = `SELECT * FROM subscription WHERE id = ?`
	var sub Subscription
	if err := s.db.GetContext(ctx, &sub, q, id); err != nil {
		return nil, fmt.Errorf("get subscription: %w", err)
	}
	return &sub, nil
}

func (s *Store) Update(ctx context.Context, id, channelName, mode string, filter json.RawMessage) error {
	const q = `UPDATE subscription SET channel_name = ?, mode = ?, filter = ? WHERE id = ?`
	_, err := s.db.ExecContext(ctx, q, channelName, mode, filter, id)
	return err
}

func (s *Store) Delete(ctx context.Context, id string) error {
	const q = `DELETE FROM subscription WHERE id = ?`
	_, err := s.db.ExecContext(ctx, q, id)
	return err
}

func (s *Store) List(ctx context.Context) ([]Subscription, error) {
	const q = `SELECT * FROM subscription ORDER BY created_at DESC`
	var subs []Subscription
	if err := s.db.SelectContext(ctx, &subs, q); err != nil {
		return nil, fmt.Errorf("list subscriptions: %w", err)
	}
	return subs, nil
}

// FindMatching returns the subscriptions in one of the given modes whose
// filter matches the target.
//
// Mode is part of the query rather than something the caller sorts out
// afterwards, because it is the coarser of the two decisions: it says what a
// channel asked to hear about at all, and only then does the filter narrow
// within it. A caller delivering an alert asks for AlertModes; one delivering
// an event asks for the event mode that fits. Nothing can leak across, which
// is what keeps a channel that subscribed to alerts from receiving a
// per-notification stream it never asked for.
func (s *Store) FindMatching(ctx context.Context, t Target, modes ...string) ([]Subscription, error) {
	all, err := s.List(ctx)
	if err != nil {
		return nil, err
	}

	wanted := make(map[string]bool, len(modes))
	for _, mode := range modes {
		wanted[mode] = true
	}

	var matched []Subscription
	for _, sub := range all {
		if !wanted[sub.Mode] {
			continue
		}
		var f SubscriptionFilter
		if err := json.Unmarshal(sub.Filter, &f); err != nil {
			continue
		}
		if f.Matches(t) {
			matched = append(matched, sub)
		}
	}
	return matched, nil
}
