package mysqlbenefactor

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

	"git.gocasts.ir/ebhomengo/niki/entity"
	errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
	richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
	"git.gocasts.ir/ebhomengo/niki/repository/mysql"
)

func (d *DB) IsExistBenefactorByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Benefactor, error) {
	const op = "mysqlbenefactor.IsExistBenefactorByPhoneNumber"

	query := `select * from benefactors where phone_number = ?`
	//nolint
	stmt, err := d.conn.PrepareStatement(ctx, mysql.StatementKeyBenefactorIsExistByPhoneNumber, query)
	if err != nil {
		return false, entity.Benefactor{}, richerror.New(op).WithErr(err).
			WithMessage(errmsg.ErrorMsgCantPrepareStatement).WithKind(richerror.KindUnexpected)
	}

	row := stmt.QueryRowContext(ctx, phoneNumber)
	Benefactor, err := scanBenefactor(row)
	if err != nil {
		sErr := sql.ErrNoRows
		//TODO-errorsas: second argument to errors.As should not be *error
		//nolint
		if errors.As(err, &sErr) {
			return false, entity.Benefactor{}, nil
		}

		// TODO - log unexpected error for better observability
		return false, entity.Benefactor{}, richerror.New(op).WithErr(err).
			WithMessage(errmsg.ErrorMsgCantScanQueryResult).WithKind(richerror.KindUnexpected)
	}

	return true, Benefactor, nil
}

func (d *DB) IsExistBenefactorByID(ctx context.Context, id uint) (bool, error) {
	const op = "mysqlbenefactor.IsExistBenefactorByID"

	query := `select * from benefactors where id = ?`
	//nolint
	stmt, err := d.conn.PrepareStatement(ctx, mysql.StatementKeyBenefactorIsExistByID, query)
	if err != nil {
		return false, richerror.New(op).WithErr(err).
			WithMessage(errmsg.ErrorMsgCantPrepareStatement).WithKind(richerror.KindUnexpected)
	}

	row := stmt.QueryRowContext(ctx, id)
	_, err = scanBenefactor(row)
	if err != nil {
		sErr := sql.ErrNoRows
		//TODO-errorsas: second argument to errors.As should not be *error
		//nolint
		if errors.As(err, &sErr) {
			return false, nil
		}

		// TODO - log unexpected error for better observability
		return false, richerror.New(op).WithErr(err).
			WithMessage(errmsg.ErrorMsgCantScanQueryResult).WithKind(richerror.KindUnexpected)
	}

	return true, nil
}

func scanBenefactor(scanner mysql.Scanner) (entity.Benefactor, error) {
	var createdAt, updatedAt time.Time
	var benefactor entity.Benefactor
	// TODO - use db model and mapper between entity and db model OR use this approach

	var benefactorNullableFields nullableFields

	err := scanner.Scan(&benefactor.ID, &benefactorNullableFields.firstName,
		&benefactorNullableFields.lastName, &benefactor.PhoneNumber, &benefactorNullableFields.description,
		&benefactorNullableFields.email, &benefactorNullableFields.genderStr,
		&benefactorNullableFields.birthdate, &createdAt, &updatedAt)

	mapNotNullToBenefactor(benefactorNullableFields, &benefactor)

	return benefactor, err
}

type nullableFields struct {
	firstName   sql.NullString
	lastName    sql.NullString
	description sql.NullString
	email       sql.NullString
	genderStr   sql.NullString
	birthdate   sql.NullTime
}

// TODO - find the other solution.
func mapNotNullToBenefactor(data nullableFields, benefactor *entity.Benefactor) {
	if data.firstName.Valid {
		benefactor.FirstName = data.firstName.String
	}
	if data.lastName.Valid {
		benefactor.LastName = data.lastName.String
	}

	if data.description.Valid {
		benefactor.Description = data.description.String
	}
	if data.email.Valid {
		benefactor.Email = data.email.String
	}

	if data.genderStr.Valid {
		benefactor.Gender = entity.Gender(data.genderStr.String)
	}
	if data.birthdate.Valid {
		benefactor.BirthDate = data.birthdate.Time
	}
}

func (d *DB) GetByID(ctx context.Context, benefactorID uint) (entity.Benefactor, error) {
	const op = "mysqlbenefactor.IsExistBenefactorByID"

	row := d.conn.Conn().QueryRowContext(ctx, `select * from benefactors where id = ?`, benefactorID)

	bnf, err := scanBenefactor(row)
	if err != nil {
		sErr := sql.ErrNoRows
		//TODO-errorsas: second argument to errors.As should not be *error
		//nolint
		if errors.As(err, &sErr) {
			return bnf, nil
		}

		// TODO - log unexpected error for better observability
		return bnf, richerror.New(op).WithErr(err).
			WithMessage(errmsg.ErrorMsgCantScanQueryResult).WithKind(richerror.KindUnexpected)
	}

	return bnf, nil
}