package postgres

import (
	"context"

	db "code.justin.tv/eventbus/controlplane/internal/db"
)

const (
	PublicationsTableName = "publications"

	publicationSelectBase = `
		SELECT id, account_id, iam_role_id, event_stream_id
	`

	publicationCreateQuery = `
		INSERT INTO publications (account_id, iam_role_id, event_stream_id)
		VALUES (:account_id, :iam_role_id, :event_stream_id) RETURNING id;
	`
	publicationDeleteQuery = `
		DELETE FROM publications
		WHERE id = $1;
	`

	publicationDeleteByEventStreamIDQuery = `
		DELETE FROM publications
		WHERE event_stream_id = $1;
	`

	publicationsByIAMRoleIDQuery = publicationSelectBase + `
		FROM publications
		WHERE iam_role_id = $1;
	`

	publicationsAllQuery = publicationSelectBase + `
		FROM publications
		ORDER BY account_id, iam_role_id, event_stream_id
		;
	`

	// Get all the accounts for a given service; then, join those
	// accounts against the ones that are found in the publications table.
	publicationsByServiceIDQuery = publicationSelectBase + `
		FROM publications
		WHERE account_id IN (
			SELECT id AS account_id
			FROM accounts
			WHERE service_id = $1
		)
		OR iam_role_id IN (
			SELECT id as iam_role_id
			FROM iam_roles
			WHERE service_id = $1
		)
		ORDER BY account_id, iam_role_id, event_stream_id
		;
	`

	publicationsByEventStreamIDQuery = publicationSelectBase + `
		FROM publications
		WHERE event_stream_id = $1
		ORDER BY account_id, iam_role_id
		;
	`
)

func (pg *PostgresDB) PublicationCreate(ctx context.Context, publication *db.Publication) (int, error) {
	var id int

	namedQuery, err := pg.writer.PrepareNamedContext(ctx, publicationCreateQuery)
	if err != nil {
		return -1, err
	}

	err = namedQuery.QueryRowxContext(ctx, publication).Scan(&id)
	return id, err
}

func (pg *PostgresDB) PublicationDelete(ctx context.Context, id int) error {
	deleteRes, err := pg.writer.ExecContext(ctx, publicationDeleteQuery, id)
	if err != nil {
		return err
	}

	numRowsAffected, err := deleteRes.RowsAffected()
	if err != nil {
		return err
	}

	if numRowsAffected == 0 {
		return db.ErrPublicationNotFound
	}

	return nil
}

func (pg *PostgresDB) PublicationDeleteByEventStreamID(ctx context.Context, eventStreamID int) error {
	deleteRes, err := pg.writer.ExecContext(ctx, publicationDeleteByEventStreamIDQuery, eventStreamID)
	if err != nil {
		return err
	}

	numRowsAffected, err := deleteRes.RowsAffected()
	if err != nil {
		return err
	}

	if numRowsAffected == 0 {
		return db.ErrPublicationNotFound
	}

	return nil
}

func (pg *PostgresDB) Publications(ctx context.Context) ([]*db.Publication, error) {
	var publications []*db.Publication
	err := pg.reader.SelectContext(ctx, &publications, publicationsAllQuery)
	return publications, err
}

func (pg *PostgresDB) PublicationsByServiceID(ctx context.Context, serviceID int) ([]*db.Publication, error) {
	var publications []*db.Publication
	err := pg.reader.SelectContext(ctx, &publications, publicationsByServiceIDQuery, serviceID)
	return publications, err
}

func (pg *PostgresDB) PublicationsByIAMRoleID(ctx context.Context, iamRoleID int) ([]*db.Publication, error) {
	var publications []*db.Publication
	err := pg.reader.SelectContext(ctx, &publications, publicationsByIAMRoleIDQuery, iamRoleID)
	return publications, err
}

func (pg *PostgresDB) PublicationsByEventStreamID(ctx context.Context, eventStreamID int) ([]*db.Publication, error) {
	var publications []*db.Publication
	err := pg.reader.SelectContext(ctx, &publications, publicationsByEventStreamIDQuery, eventStreamID)
	return publications, err
}
