package billing

import (
	"context"
	"errors"

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

var errNotFound = errors.New("not found")

type repository interface {
	getSubscriptionByNetworkID(ctx context.Context, networkID string) (*Subscription, error)
	upsertSubscription(ctx context.Context, sub *Subscription) error
	deleteSubscriptionByID(ctx context.Context, subscriptionID string) error

	getStripeCustomerID(ctx context.Context, networkID string) (string, error)
	setStripeCustomerID(ctx context.Context, networkID, customerID string) error
}

type repositoryImpl struct {
	pool *pgxpool.Pool
}

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

const subscriptionColumns = `id, network_id, stripe_customer_id, status, price_id, cadence, quantity, cancel_at_period_end, current_period_start, current_period_end, created_at, updated_at`

func scanSubscription(row pgx.Row, s *Subscription) error {
	return row.Scan(
		&s.ID, &s.NetworkID, &s.StripeCustomerID, &s.Status, &s.PriceID,
		&s.Cadence, &s.Quantity, &s.CancelAtPeriodEnd,
		&s.CurrentPeriodStart, &s.CurrentPeriodEnd,
		&s.CreatedAt, &s.UpdatedAt,
	)
}

func (r *repositoryImpl) getSubscriptionByNetworkID(ctx context.Context, networkID string) (*Subscription, error) {
	var s Subscription
	err := scanSubscription(
		r.pool.QueryRow(ctx,
			`SELECT `+subscriptionColumns+` FROM network_subscriptions WHERE network_id = $1`,
			networkID,
		),
		&s,
	)
	if errors.Is(err, pgx.ErrNoRows) {
		return nil, errNotFound
	}
	if err != nil {
		return nil, err
	}
	return &s, nil
}

func (r *repositoryImpl) upsertSubscription(ctx context.Context, sub *Subscription) error {
	_, err := r.pool.Exec(ctx, `
		INSERT INTO network_subscriptions (
			id, network_id, stripe_customer_id, status, price_id, cadence,
			quantity, cancel_at_period_end, current_period_start, current_period_end
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
		ON CONFLICT (id) DO UPDATE SET
			stripe_customer_id   = EXCLUDED.stripe_customer_id,
			status               = EXCLUDED.status,
			price_id             = EXCLUDED.price_id,
			cadence              = EXCLUDED.cadence,
			quantity             = EXCLUDED.quantity,
			cancel_at_period_end = EXCLUDED.cancel_at_period_end,
			current_period_start = EXCLUDED.current_period_start,
			current_period_end   = EXCLUDED.current_period_end,
			updated_at           = NOW()
	`,
		sub.ID, sub.NetworkID, sub.StripeCustomerID, sub.Status, sub.PriceID, sub.Cadence,
		sub.Quantity, sub.CancelAtPeriodEnd, sub.CurrentPeriodStart, sub.CurrentPeriodEnd,
	)
	return err
}

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

func (r *repositoryImpl) getStripeCustomerID(ctx context.Context, networkID string) (string, error) {
	var customerID string
	err := r.pool.QueryRow(ctx,
		`SELECT stripe_customer_id FROM network_stripe_customers WHERE network_id = $1`,
		networkID,
	).Scan(&customerID)
	if errors.Is(err, pgx.ErrNoRows) {
		return "", errNotFound
	}
	if err != nil {
		return "", err
	}
	return customerID, nil
}

func (r *repositoryImpl) setStripeCustomerID(ctx context.Context, networkID, customerID string) error {
	_, err := r.pool.Exec(ctx, `
		INSERT INTO network_stripe_customers (network_id, stripe_customer_id)
		VALUES ($1, $2)
		ON CONFLICT (network_id) DO NOTHING
	`, networkID, customerID)
	return err
}
