package postgres

import (
	"context"
	"database/sql"
	"strconv"

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

const (
	AccountsTableName = "accounts"

	createAccountQuery = `
INSERT INTO accounts (aws_account_id, label, service_id)
VALUES (:aws_account_id, :label, :service_id)
RETURNING id
`
	selectAccountsBaseQuery = `
SELECT * FROM accounts
`

	selectAllAccountsQuery = selectAccountsBaseQuery

	selectAccountByIDQuery = selectAccountsBaseQuery + `
WHERE id = $1
`

	selectAccountsByServiceIDQuery = selectAccountsBaseQuery + `
WHERE service_id = $1
ORDER BY aws_account_id
`

	selectAccountsByNameQuery = selectAccountsBaseQuery + `
WHERE name = $1
ORDER BY aws_account_id
`

	selectAccountByServiceIDAndAWSAccountQuery = selectAccountsBaseQuery + `
WHERE service_id = $1 AND aws_account_id = $2
LIMIT 1
`

	deleteAccountQuery = `
DELETE FROM accounts 
WHERE id = $1`

	updateAccountCloudformationStatusQuery = `
UPDATE accounts
SET cloudformation_status = $1
WHERE id = $2
`
)

func (pg *PostgresDB) AccountCreate(ctx context.Context, account *db.Account) (int, error) {
	rows, err := pg.writer.NamedQueryContext(ctx, createAccountQuery, account)
	if err != nil {
		return -1, errors.Wrap(err, "could not insert account into db")
	}
	defer rows.Close()

	var id int
	rows.Next()
	err = rows.Scan(&id)
	if err != nil {
		return -1, errors.Wrap(err, "could not retrieve inserted account id")
	}

	return id, nil
}

func (pg *PostgresDB) AccountUpdate(ctx context.Context, id int, accountEditable *db.AccountEditable) (int, error) {
	query := "update accounts set label = :label where id = " + strconv.Itoa(id)

	res, err := pg.writer.NamedExecContext(ctx, query, accountEditable)
	if err != nil {
		return id, errors.Wrap(err, "could not update account")
	}
	countUpdated, err := res.RowsAffected()
	if err != nil {
		return id, errors.Wrap(err, "could not count rows affected")
	}
	if countUpdated == 0 {
		return id, db.ErrResourceNotFound
	}

	return id, nil
}

func (pg *PostgresDB) AccountUpdateCloudformationStatus(ctx context.Context, id int, cloudformationStatus string) (int, error) {
	updateRes, err := pg.writer.ExecContext(ctx, updateAccountCloudformationStatusQuery, cloudformationStatus, id)
	if err != nil {
		return -1, err
	}

	countUpdated, err := updateRes.RowsAffected()
	if err != nil {
		return -1, errors.Wrap(err, "could not count rows affected")
	}
	if countUpdated == 0 {
		return -1, db.ErrResourceNotFound
	}

	return id, nil
}

func (pg *PostgresDB) Accounts(ctx context.Context) ([]*db.Account, error) {
	var accounts []*db.Account
	err := pg.reader.SelectContext(ctx, &accounts, selectAllAccountsQuery)
	return accounts, err
}

func (pg *PostgresDB) AccountByID(ctx context.Context, id int) (*db.Account, error) {
	var account db.Account
	err := pg.reader.GetContext(ctx, &account, selectAccountByIDQuery, id)
	if err == sql.ErrNoRows {
		return nil, db.ErrResourceNotFound
	}
	return &account, err
}

func (pg *PostgresDB) AccountsByServiceID(ctx context.Context, serviceID int) ([]*db.Account, error) {
	var accounts []*db.Account
	err := pg.reader.SelectContext(ctx, &accounts, selectAccountsByServiceIDQuery, serviceID)
	return accounts, err
}

func (pg *PostgresDB) AccountByName(ctx context.Context, name string) (db.Account, error) {
	var account db.Account
	err := pg.reader.GetContext(ctx, &account, selectAccountsByNameQuery, name)
	if err == sql.ErrNoRows {
		return account, db.ErrResourceNotFound
	}
	return account, err
}

func (pg *PostgresDB) AccountByServiceIDAndAWSAccount(ctx context.Context, serviceID int, awsAccount string) (*db.Account, error) {
	var account db.Account
	err := pg.reader.GetContext(ctx, &account, selectAccountByServiceIDAndAWSAccountQuery, serviceID, awsAccount)
	if err == sql.ErrNoRows {
		return nil, db.ErrResourceNotFound
	}
	return &account, err
}

func (pg *PostgresDB) AccountDelete(ctx context.Context, id int) error {
	row, err := pg.writer.ExecContext(ctx, deleteAccountQuery, id)
	if err != nil {
		return errors.Wrap(err, "failed to delete from accounts")
	}

	countDeleted, err := row.RowsAffected()
	if err != nil {
		return errors.Wrap(err, "could not count rows affected")
	}

	if countDeleted == 0 {
		return db.ErrResourceNotFound
	}

	return nil
}
