package store

import (
	"database/sql"
	"fmt"
	"time"

	sq "github.com/Masterminds/squirrel"

	"git.ctrlz.es/mgdelacroix/craban/model"
	"git.ctrlz.es/mgdelacroix/craban/utils"
)

var gameColumns = []string{"id", "user_id", "name"}
var gameMemberColumns = []string{"game_id", "user_id", "role", "lastviewedat"}

type GameStore struct {
	Conn *sql.DB
}

func NewGameStore(s *Store) *GameStore {
	return &GameStore{Conn: s.Conn}
}

func (gs *GameStore) Q() sq.StatementBuilderType {
	return sq.StatementBuilder.RunWith(gs.Conn)
}

func (gs *GameStore) gamesFromRows(rows *sql.Rows) ([]*model.Game, error) {
	games := []*model.Game{}

	for rows.Next() {
		var game model.Game

		err := rows.Scan(
			&game.ID,
			&game.UserID,
			&game.Name,
		)
		if err != nil {
			return nil, err
		}

		games = append(games, &game)
	}

	return games, nil
}

func (gs *GameStore) getGameByCondition(condition sq.Eq) (*model.Game, error) {
	games, err := gs.getGamesByCondition(condition)
	if err != nil {
		return nil, err
	}

	return games[0], nil
}

func (gs *GameStore) getGamesByCondition(condition sq.Eq) ([]*model.Game, error) {
	rows, err := gs.Q().Select(gameColumns...).
		From("games").
		Where(condition).
		Query()
	if err != nil {
		return nil, fmt.Errorf("cannot get games: %w", err)
	}
	defer rows.Close()

	games, err := gs.gamesFromRows(rows)
	if err != nil {
		return nil, fmt.Errorf("cannot get games from rows: %w", err)
	}

	if len(games) == 0 {
		return nil, sql.ErrNoRows
	}

	return games, nil
}

func (gs *GameStore) GetByID(id int) (*model.Game, error) {
	return gs.getGameByCondition(sq.Eq{"id": id})
}

func (gs *GameStore) Create(name string, userID int) (*model.Game, error) {
	// ToDo: generate transaction
	// ToDo: add tests

	res, err := gs.Q().Insert("games").
		Columns(gameColumns[1:]...).
		Values(userID, name).
		Exec()
	if err != nil {
		return nil, fmt.Errorf("cannot insert game: %w", err)
	}

	id, err := res.LastInsertId()
	if err != nil {
		return nil, fmt.Errorf("cannot get last insert id for created game: %w", err)
	}

	game, err := gs.GetByID(int(id))
	if err != nil {
		return nil, fmt.Errorf("cannot get game by id: %w", err)
	}

	res, err = gs.Q().Insert("gamemembers").
		Columns(gameMemberColumns...).
		Values(game.ID, userID, model.RoleGameMaster, 0).
		Exec()
	if err != nil {
		return nil, fmt.Errorf("cannot insert gamemember for created game: %w", err)
	}

	return game, nil
}

func (gs *GameStore) UpdateLastViewedAt(gameID, userID int) error {
	_, err := gs.Q().Update("gamemembers").
		Set("lastviewedat", utils.Millis(time.Now())).
		Where(sq.Eq{"game_id": gameID, "user_id": userID}).
		Exec()
	return err
}

func (gs *GameStore) gameMembersFromRows(rows *sql.Rows) ([]*model.GameMember, error) {
	gameMembers := []*model.GameMember{}

	for rows.Next() {
		var gameMember model.GameMember

		err := rows.Scan(
			&gameMember.GameID,
			&gameMember.UserID,
			&gameMember.Role,
			&gameMember.LastViewedAt,
		)
		if err != nil {
			return nil, err
		}

		gameMembers = append(gameMembers, &gameMember)
	}

	return gameMembers, nil
}

func (gs *GameStore) GetMembers(gameID int) ([]*model.GameMember, error) {
	rows, err := gs.Q().Select(gameMemberColumns...).
		From("gamemembers").
		Where(sq.Eq{"game_id": gameID}).
		Query()
	if err != nil {
		return nil, fmt.Errorf("cannot get game members: %w", err)
	}
	defer rows.Close()

	gameMembers, err := gs.gameMembersFromRows(rows)
	if err != nil {
		return nil, fmt.Errorf("cannot get game members from rows: %w", err)
	}

	if len(gameMembers) == 0 {
		return nil, sql.ErrNoRows
	}

	return gameMembers, nil
}

func (gs *GameStore) GetMember(gameID, userID int) (*model.GameMember, error) {
	rows, err := gs.Q().Select(gameMemberColumns...).
		From("gamemembers").
		Where(sq.Eq{"game_id": gameID, "user_id": userID}).
		Query()
	if err != nil {
		return nil, fmt.Errorf("cannot get game member: %w", err)
	}
	defer rows.Close()

	gameMembers, err := gs.gameMembersFromRows(rows)
	if err != nil {
		return nil, fmt.Errorf("cannot get game member from rows: %w", err)
	}

	if len(gameMembers) == 0 {
		return nil, sql.ErrNoRows
	}

	return gameMembers[0], nil
}

func (gs *GameStore) AddMember(gameID, userID int, role string) (*model.GameMember, error) {
	_, err := gs.Q().Insert("gamemembers").
		Columns(gameMemberColumns...).
		Values(gameID, userID, role, 0).
		Exec()
	if err != nil {
		return nil, fmt.Errorf("cannot insert gamemember: %w", err)
	}

	return gs.GetMember(gameID, userID)
}

func (gs *GameStore) ListForUser(userID int) ([]*model.Game, error) {
	return gs.getGamesByCondition(sq.Eq{"user_id": userID})
}

func (gs *GameStore) List() ([]*model.Game, error) {
	return gs.getGamesByCondition(sq.Eq{})
}
