package model

import (
	"database/sql"
	"errors"
	"fmt"
)

type Ingredient struct {
	Id     string `json:"id"`
	Index  string `json:"index"`
	Amount string `json:"amount"`
	Unit   string `json:"unit"`
	Type   string `json:"type"`
	Step   string `json:"step"`
}

func (i *Ingredient) Validate() error {
	var err error

	if i.Id != "" {
		err = isPositiveOrZeroInt(i.Id)
		if err != nil {
			return fmt.Errorf("Invalid ingredient ID: %w", err)
		}
	}

	err = isPositiveOrZeroInt(i.Index)
	if err != nil {
		return fmt.Errorf("Invalid ingredient index: %w", err)
	}

	if i.Amount != "" {
		err = isPositiveOrZeroFloat(i.Amount)
		if err != nil {
			return fmt.Errorf("Invalid ingredient amount: %w", err)
		}
	}

	err = isPositiveOrZeroInt(i.Step)
	if err != nil {
		return fmt.Errorf("Ingredient does not reference a valid step ID: %w", err)
	}

	return nil
}

func (i *Ingredient) Create(tx *sql.Tx) error {
	if i.Id != "" {
		return fmt.Errorf("Cannot create ingredient if ID is given")
	}

	err := i.Validate()
	if err != nil {
		return err
	}

	cmd := `
INSERT INTO ingredients
    ('index', amount, unit, 'type', step)
VALUES
    (?, ?, ?, ?, ?)
`

	result, err := tx.Exec(cmd, i.Index, i.Amount, i.Unit, i.Type, i.Step)
	if err != nil {
		return err
	}

	id, err := result.LastInsertId()
	if err != nil {
		return err
	}

	i.Id = fmt.Sprint(id)

	return nil
}

func (i *Ingredient) Read(tx *sql.Tx) error {
	cmd := `
SELECT
    "index", amount, unit, "type", step
FROM
    ingredients
WHERE
    id = ?
`

	rows, err := tx.Query(cmd, i.Id)
	if err != nil {
		return err
	}
	defer rows.Close()

	if !rows.Next() {
		return sql.ErrNoRows
	}

	err = rows.Scan(&i.Index, &i.Amount, &i.Unit, &i.Type, &i.Step)
	if err != nil {
		return err
	}

	return i.Validate()
}

func (i *Ingredient) Update(tx *sql.Tx) error {
	err := i.Validate()
	if err != nil {
		return err
	}

	cmd := `
UPDATE
    ingredients
SET
    index = ?,
    amount = ?,
    unit = ?,
    type = ?,
    step = ?
WHERE
    id = ?`

	res, err := tx.Exec(cmd, i.Index, i.Amount, i.Unit, i.Type, i.Step, i.Id)
	if err != nil {
		return err
	}

	affected, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if affected != 1 {
		return fmt.Errorf("Ingredient update affected %d rows instead of 1", affected)
	}

	return nil
}

func (i *Ingredient) Delete(tx *sql.Tx) error {
	cmd := `
DELETE FROM
    ingredients
WHERE
    id = ?
`

	result, err := tx.Exec(cmd, i.Id)
	if err != nil {
		return err
	}

	rows, err := result.RowsAffected()
	if rows != 1 {
		return errors.New("Ingredient deletion did not affect exactly one row")
	}

	return nil
}