package pgdb

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strings"

	dberrors "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/errors"
	dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
	"github.com/Masterminds/squirrel"
	"github.com/google/uuid"
)

// ALERT: intergrate safety checks

func (c *client) GetBankAccountList(
	ctx context.Context,
	request *dbtypes.BankAccountListGetRequest,
) (*dbtypes.BankAccountListGetResponse, error) {
	if request == nil {
		return nil, nil
	}

	var (
		psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)

		bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
	)

	getAccounts := psql.Select(
		"id", "owner_id", "account_number", "bank_name", "bik",
		"correspondent_account", "is_primary", "created_at", "updated_at",
	).From(bankAccountsTable).
		Where(squirrel.Eq{"owner_id": request.OwnerId})

	query, args, err := getAccounts.ToSql()
	if err != nil {
		return nil, fmt.Errorf("%w: error building get bank accounts query: %v", dberrors.ErrInternal, err)
	}

	rows, err := c.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("%w: error executing get bank accounts query: %v", dberrors.ErrInternal, err)
	}

	defer rows.Close()

	var accounts []dbtypes.BankAccount

	for rows.Next() {
		var (
			accountNumber, bankName, bik, correspondentAccount sql.NullString
			bankAccount                                        dbtypes.BankAccount
		)

		if err := rows.Scan(
			&bankAccount.Id, &bankAccount.OwnerId, &accountNumber, &bankName, &bik,
			&correspondentAccount, &bankAccount.IsPrimary, &bankAccount.CreatedAt, &bankAccount.UpdatedAt,
		); err != nil {
			if errors.Is(err, sql.ErrNoRows) {
				return nil, dberrors.ErrNotFound
			}

			return nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
		}

		if accountNumber.Valid {
			bankAccount.AccountName = accountNumber.String
		}

		if bankName.Valid {
			bankAccount.BankName = bankName.String
		}

		if bik.Valid {
			bankAccount.Bik = bik.String
		}

		if correspondentAccount.Valid {
			bankAccount.CorrespondentAccount = correspondentAccount.String
		}

		accounts = append(accounts, bankAccount)
	}

	return &dbtypes.BankAccountListGetResponse{
		BankAccounts: accounts,
	}, nil
}

func (c *client) CreateBankAccount(
	ctx context.Context,
	request *dbtypes.BankAccountCreateRequest,
) (*dbtypes.BankAccountCreateResponse, error) {
	if request == nil {
		return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
	}

	var (
		psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)

		bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
	)

	// TODO: use normal uuid after DB reengineering
	bankAccountId := fmt.Sprintf("%sBNK", strings.ReplaceAll(uuid.NewString(), "-", ""))

	createBankAccount := psql.Insert(bankAccountsTable).
		Columns(
			"id", "owner_id", "account_number", "bank_name", "bik", "correspondent_account", "is_primary",
		).
		Values(
			bankAccountId, request.OwnerId, request.AccountNumber, request.BankName,
			request.Bik, request.CorrespondentAccount, request.IsPrimary,
		)

	query, args, err := createBankAccount.ToSql()
	if err != nil {
		return nil, fmt.Errorf("%w: error building create bank account query: %v", dberrors.ErrInternal, err)
	}

	res, err := c.db.ExecContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("%w: error executing create bank account query: %v", dberrors.ErrInternal, err)
	}

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		return nil, fmt.Errorf("%w: error getting rows affected for create bank account query: %v", dberrors.ErrInternal, err)
	}

	if rowsAffected == 0 {
		return nil, dberrors.ErrInternal
	}

	return &dbtypes.BankAccountCreateResponse{
		Id: bankAccountId,
	}, nil
}

func (c *client) UpdateBankAccount(
	ctx context.Context,
	request *dbtypes.BankAccountUpdateRequest,
) (*dbtypes.BankAccountUpdateResponse, error) {
	if request == nil {
		return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
	}

	tx, err := c.db.BeginTx(ctx, nil)
	if err != nil {
		return nil, fmt.Errorf("%w: error starting transaction: %v", dberrors.ErrInternal, err)
	}

	defer func() { _ = tx.Rollback() }()

	result, err := c.updateBankAccount(ctx, tx, request)
	if err != nil {
		return nil, fmt.Errorf("error updating bank account: %w", err)
	}

	if err := tx.Commit(); err != nil {
		return nil, fmt.Errorf("%w: error committing transaction: %w", dberrors.ErrInternal, err)
	}

	return result, nil
}

func (c *client) updateBankAccount(
	ctx context.Context,
	driver Driver,
	request *dbtypes.BankAccountUpdateRequest,
) (*dbtypes.BankAccountUpdateResponse, error) {
	var (
		psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)

		bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
	)

	updateAccount := psql.Update(bankAccountsTable).
		Where(squirrel.Eq{"id": request.Id})

	if request.AccountNumber != nil {
		updateAccount = updateAccount.Set("account_number", *request.AccountNumber)
	}

	// TODO: uncomment when DB supports it
	// if request.AccountName != nil {
	// 	updateAccount = updateAccount.Set("account_name", *request.AccountName)
	// }

	if request.BankName != nil {
		updateAccount = updateAccount.Set("bank_name", *request.BankName)
	}

	if request.Bik != nil {
		updateAccount = updateAccount.Set("bik", *request.Bik)
	}

	if request.CorrespondentAccount != nil {
		updateAccount = updateAccount.Set("correspondent_account", *request.CorrespondentAccount)
	}

	if request.IsPrimary != nil {
		if *request.IsPrimary {
			if err := c.unmarkPrimaryBankAccount(ctx, driver, request.Id); err != nil {
				return nil, fmt.Errorf("error unmarking currently primary bank account: %w", err)
			}
		}

		// QnA: what if the update makes all BAs non-primary?
		updateAccount = updateAccount.Set("is_primary", *request.IsPrimary)
	}

	query, args, err := updateAccount.ToSql()
	if err != nil {
		return nil, fmt.Errorf("%w: error building 'update bank account' query: %v", dberrors.ErrInternal, err)
	}

	res, err := driver.ExecContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("%w: error executing 'update bank account' query: %v", dberrors.ErrInternal, err)
	}

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		return nil, fmt.Errorf("%w: error getting rows affected for 'update bank account' query: %v", dberrors.ErrInternal, err)
	}

	if rowsAffected == 0 {
		return nil, dberrors.ErrNotFound
	}

	return &dbtypes.BankAccountUpdateResponse{}, nil
}

func (c *client) unmarkPrimaryBankAccount(
	ctx context.Context,
	driver Driver,
	bankAccountId string,
) error {
	var (
		psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)

		bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
	)

	getOwnerId := psql.Select(
		"owner_id", "is_primary",
	).From(bankAccountsTable).
		Where(squirrel.Eq{"id": bankAccountId}).
		Suffix("FOR UPDATE")

	query, args, err := getOwnerId.ToSql()
	if err != nil {
		return fmt.Errorf("%w: error building 'get owner id of bank account' query: %v", dberrors.ErrInternal, err)
	}

	row := driver.QueryRowContext(ctx, query, args...)

	var (
		ownerId   string
		isPrimary bool
	)

	if err := row.Scan(&ownerId, &isPrimary); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return dberrors.ErrNotFound
		}

		return fmt.Errorf("%w: error scanning row for 'get owner id of bank account' query: %v", dberrors.ErrInternal, err)
	}

	if isPrimary {
		return nil
	}

	unmarkPrimaryBA := psql.Update(bankAccountsTable).
		Set("is_primary", false).
		Where(squirrel.Eq{
			"owner_id":   ownerId,
			"is_primary": true,
		})

	query, args, err = unmarkPrimaryBA.ToSql()
	if err != nil {
		return fmt.Errorf("%w: error building 'unmark primary bank account' query: %v", dberrors.ErrInternal, err)
	}

	res, err := driver.ExecContext(ctx, query, args...)
	if err != nil {
		return fmt.Errorf("%w: error executing 'unmark primary bank account' query: %v", dberrors.ErrInternal, err)
	}

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		return fmt.Errorf("%w: error getting rows affected for 'unmark primary bank account' query: %v", dberrors.ErrInternal, err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("%w: error unmarking primary bank account: no rows affected", dberrors.ErrInternal)
	}

	return nil
}

func (c *client) DeleteBankAccount(
	ctx context.Context,
	request *dbtypes.BankAccountDeleteRequest,
) (*dbtypes.BankAccountDeleteResponse, error) {
	return nil, dberrors.ErrUnimplemented
}
