package postgres

import (
	"context"
	"database/sql"
	"time"

	"code.justin.tv/eventbus/controlplane/internal/db"
	"github.com/pkg/errors"
)

const (
	SubscriptionTargetsTableName = "subscription_targets"

	subscriptionTargetSelectBase = `
SELECT
	subscription_targets.id,
	subscription_targets.name,
	subscription_targets.assume_role_arn,
	subscription_targets.sqs_queue_arn "sqs_details.sqs_queue_arn",
	subscription_targets.sqs_queue_url "sqs_details.sqs_queue_url",
	subscription_targets.deadletter_queue_arn "sqs_details.deadletter_queue_arn",
	subscription_targets.deadletter_queue_url "sqs_details.deadletter_queue_url",
	subscription_targets.retention_profile_id,
	subscription_targets.status,
	subscription_targets.error,
	subscription_targets.service_id,
	subscription_targets.aws_lease_id
FROM subscription_targets
`

	subscriptionTargetCreateQuery = `
INSERT INTO subscription_targets (
	name, assume_role_arn, sqs_queue_arn, sqs_queue_url, deadletter_queue_arn, deadletter_queue_url, 
	status, error, service_id, retention_profile_id)
VALUES (
	:name, :assume_role_arn, :sqs_details.sqs_queue_arn, :sqs_details.sqs_queue_url, 
	:sqs_details.deadletter_queue_arn, :sqs_details.deadletter_queue_url, 
	:status, :error, :service_id, :retention_profile_id)
RETURNING id
`

	subscriptionTargetUpdateQuery = `
UPDATE subscription_targets
SET name = $2
WHERE id = $1
`

	subscriptionTargetUpdateInfraQuery = `
UPDATE subscription_targets
SET error = $2, status = $3, sqs_queue_arn = $4, sqs_queue_url = $5, deadletter_queue_arn = $6, deadletter_queue_url = $7
WHERE id = $1
`

	subscriptionTargetAllQuery = subscriptionTargetSelectBase + `
ORDER BY name`

	subscriptionTargetByIDQuery = subscriptionTargetSelectBase + `
WHERE id = $1
`

	subscriptionTargetByQueueURLQuery = subscriptionTargetSelectBase + `
WHERE sqs_queue_url = $1
`

	subscriptionTargetsByServiceIDQuery = subscriptionTargetSelectBase + `
WHERE service_id = $1
ORDER BY name
`

	subscriptionTargetDeleteQuery = `
DELETE FROM subscription_targets
WHERE id = $1
`
)

func (pg *PostgresDB) SubscriptionTargetCreate(ctx context.Context, subscriptionTarget *db.SubscriptionTarget) (int, error) {
	var id int
	namedQuery, err := pg.writer.PrepareNamedContext(ctx, subscriptionTargetCreateQuery)
	if err != nil {
		return -1, err
	}

	err = namedQuery.QueryRowxContext(ctx, subscriptionTarget).Scan(&id)
	if err != nil {
		return -1, pgError(err, SubscriptionTargetsTableName)
	}
	return id, nil
}

func (pg *PostgresDB) SubscriptionTargetUpdate(ctx context.Context, id int, subscriptionTargetEditable *db.SubscriptionTargetEditable) (int, error) {
	res, err := pg.writer.ExecContext(ctx, subscriptionTargetUpdateQuery, id, subscriptionTargetEditable.Name)
	if err != nil {
		return -1, err
	}

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		return -1, err
	}

	if rowsAffected == 0 {
		return -1, db.ErrResourceNotFound
	}
	return id, nil
}

func (pg *PostgresDB) SubscriptionTargetUpdateInfra(ctx context.Context, lease db.AWSLease, id int, subscriptionTargetInfraUpdate *db.SubscriptionTargetInfraUpdate) (int, error) {
	if lease == nil {
		return -1, errors.New("nil lease object passed to SubscriptionTargetsTableName.UpdateInfra")
	}

	if lease.Expired() {
		return -1, db.ErrLeaseExpired
	}

	sqsDetails := subscriptionTargetInfraUpdate.SQSDetails

	res, err := pg.writer.ExecContext(
		ctx, subscriptionTargetUpdateInfraQuery, id, subscriptionTargetInfraUpdate.Error, subscriptionTargetInfraUpdate.Status,
		sqsDetails.SQSQueueARN, sqsDetails.SQSQueueURL, sqsDetails.DeadletterQueueARN, sqsDetails.DeadletterQueueURL,
	)
	if err != nil {
		return -1, err
	}

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		return -1, err
	}

	if rowsAffected == 0 {
		return -1, db.ErrResourceNotFound
	}

	return id, nil
}

func (pg *PostgresDB) SubscriptionTargetDelete(ctx context.Context, lease db.AWSLease, id int) error {
	if lease == nil {
		return errors.New("nil lease object passed to SubscriptionTargetDelete")
	}

	if lease.Expired() {
		return db.ErrLeaseExpired
	}

	res, err := pg.writer.ExecContext(ctx, subscriptionTargetDeleteQuery, id)
	if err != nil {
		return err
	}

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		return err
	}

	if rowsAffected == 0 {
		return db.ErrResourceNotFound
	}

	return nil
}

func (pg *PostgresDB) SubscriptionTargets(ctx context.Context) ([]*db.SubscriptionTarget, error) {
	var targets []*db.SubscriptionTarget
	err := pg.reader.SelectContext(ctx, &targets, subscriptionTargetAllQuery)
	return targets, err
}

func (pg *PostgresDB) SubscriptionTargetByID(ctx context.Context, id int) (*db.SubscriptionTarget, error) {
	var target db.SubscriptionTarget
	err := pg.reader.GetContext(ctx, &target, subscriptionTargetByIDQuery, id)
	if err == sql.ErrNoRows {
		return nil, db.ErrResourceNotFound
	}
	return &target, err
}

func (pg *PostgresDB) SubscriptionTargetByQueueURL(ctx context.Context, name string) (*db.SubscriptionTarget, error) {
	var target db.SubscriptionTarget
	err := pg.reader.GetContext(ctx, &target, subscriptionTargetByQueueURLQuery, name)
	if err == sql.ErrNoRows {
		return nil, db.ErrResourceNotFound
	}
	return &target, err
}

func (pg *PostgresDB) SubscriptionTargetsByServiceID(ctx context.Context, serviceID int) ([]*db.SubscriptionTarget, error) {
	var targets []*db.SubscriptionTarget
	err := pg.reader.SelectContext(ctx, &targets, subscriptionTargetsByServiceIDQuery, serviceID)
	return targets, err
}

func (pg *PostgresDB) SubscriptionTargetAcquireLease(ctx context.Context, resourceID int, timeout time.Duration) (db.AWSLease, context.Context, error) {
	return pg.Acquire(ctx, resourceID, SubscriptionTargetsTableName, timeout)
}

func (pg *PostgresDB) SubscriptionTargetReleaseLease(lease db.AWSLease) error {
	return lease.Release()
}
