package pushnotify

import (
	"context"
	"errors"

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

type repository interface {
	upsert(ctx context.Context, t *PushToken) error
	deleteForHuman(ctx context.Context, humanID, token string) error
	deleteByToken(ctx context.Context, token string) error
	listForHumans(ctx context.Context, humanIDs []string) ([]*PushToken, error)
}

type repositoryImpl struct {
	pool *pgxpool.Pool
}

func newRepository(pool *pgxpool.Pool) repository {
	return &repositoryImpl{pool: pool}
}

func (r *repositoryImpl) upsert(ctx context.Context, t *PushToken) error {
	_, err := r.pool.Exec(ctx,
		`INSERT INTO push_tokens (token, human_id, platform, app_version)
		 VALUES ($1, $2, $3, NULLIF($4, ''))
		 ON CONFLICT (token) DO UPDATE SET
		     human_id     = EXCLUDED.human_id,
		     platform     = EXCLUDED.platform,
		     app_version  = EXCLUDED.app_version,
		     last_seen_at = NOW()`,
		t.Token, t.HumanID, string(t.Platform), t.AppVersion,
	)
	return err
}

func (r *repositoryImpl) deleteForHuman(ctx context.Context, humanID, token string) error {
	result, err := r.pool.Exec(ctx,
		`DELETE FROM push_tokens WHERE human_id = $1 AND token = $2`,
		humanID, token,
	)
	if err != nil {
		return err
	}
	if result.RowsAffected() == 0 {
		return ErrNotFound
	}
	return nil
}

func (r *repositoryImpl) deleteByToken(ctx context.Context, token string) error {
	_, err := r.pool.Exec(ctx,
		`DELETE FROM push_tokens WHERE token = $1`,
		token,
	)
	return err
}

func (r *repositoryImpl) listForHumans(ctx context.Context, humanIDs []string) ([]*PushToken, error) {
	if len(humanIDs) == 0 {
		return nil, nil
	}
	rows, err := r.pool.Query(ctx,
		`SELECT token, human_id, platform, app_version, created_at, last_seen_at
		 FROM push_tokens
		 WHERE human_id = ANY($1)`,
		humanIDs,
	)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, nil
		}
		return nil, err
	}
	defer rows.Close()

	var tokens []*PushToken
	for rows.Next() {
		var t PushToken
		var appVersion *string
		var platform string
		if err := rows.Scan(&t.Token, &t.HumanID, &platform, &appVersion, &t.CreatedAt, &t.LastSeenAt); err != nil {
			return nil, err
		}
		t.Platform = Platform(platform)
		if appVersion != nil {
			t.AppVersion = *appVersion
		}
		tokens = append(tokens, &t)
	}
	return tokens, rows.Err()
}
