package storage

import (
	"context"
	"database/sql"
	"status/model"
	"strconv"
	"strings"
)

type statusQueryBuilder struct {
	where  string
	limit  string
	offset string
}

func (p statusQueryBuilder) build() string {
	query := []string{`SELECT id, author, content, created_at, face, number from statuses`}
	if p.where != "" {
		query = append(query, `WHERE`, p.where)
	}
	query = append(query, `ORDER BY created_at desc`)
	if p.limit != "" {
		query = append(query, `LIMIT`, p.limit)
	}
	if p.offset != "" {
		query = append(query, `OFFSET`, p.offset)
	}
	return strings.Join(query, " ")
}

func (s *Storage) populateStatus(rows *sql.Rows) (model.Status, error) {
	var status model.Status
	err := rows.Scan(&status.Id, &status.User, &status.Content, &status.CreatedAt, &status.Face, &status.Number)
	if err != nil {
		return status, err
	}
	return status, nil
}

func (s *Storage) CreateStatus(status model.Status) error {
	var statusId int64
	ctx := context.Background()
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	if err := tx.QueryRowContext(ctx, `INSERT INTO statuses (author, content, face, number) VALUES ($1, $2, $3, $4) RETURNING id`,
		status.User, status.Content, status.Face, status.Number).Scan(&statusId); err != nil {
		tx.Rollback()
		return err
	}

	if _, err := tx.ExecContext(ctx, `UPDATE users set status_id=$1 where name=$2`, statusId, status.User); err != nil {
		tx.Rollback()
		return err
	}
	err = tx.Commit()
	return err
}

func (s *Storage) StatusById(id int64) (model.Status, error) {
	var status model.Status
	err := s.db.QueryRow(
		`SELECT id, author, content, face, created_at, number from statuses WHERE id=$1`, id).Scan(
		&status.Id,
		&status.User,
		&status.Content,
		&status.Face,
		&status.CreatedAt,
                &status.Number,
	)
	return status, err
}

func (s *Storage) StatusByUsername(user string, perPage int, page int64) ([]model.Status, bool, error) {
	rows, err := s.db.Query(statusQueryBuilder{
		where:  `author = $1`,
		limit:  strconv.Itoa(perPage + 1),
		offset: `$2`,
	}.build(), user, page*int64(perPage))
	if err != nil {
		return nil, false, err
	}
	var statuses []model.Status
	for rows.Next() {
		post, err := s.populateStatus(rows)
		if err != nil {
			return nil, false, err
		}
		statuses = append(statuses, post)
	}
	if len(statuses) > perPage {
		return statuses[0:perPage], true, err
	}
	return statuses, false, err
}

func (s *Storage) StatusFeed() ([]model.Status, error) {
	rows, err := s.db.Query(statusQueryBuilder{
		limit: strconv.Itoa(20),
	}.build())
	if err != nil {
		return nil, err
	}
	var statuses []model.Status
	for rows.Next() {
		post, err := s.populateStatus(rows)
		if err != nil {
			return nil, err
		}
		statuses = append(statuses, post)
	}
	return statuses, err
}

func (s *Storage) Statuses(page int64, perPage int) ([]model.Status, bool, error) {
	rows, err := s.db.Query(statusQueryBuilder{
		limit:  strconv.Itoa(perPage + 1),
		offset: `$1`,
	}.build(), page*int64(perPage))
	if err != nil {
		return nil, false, err
	}
	var statuses []model.Status
	for rows.Next() {
		post, err := s.populateStatus(rows)
		if err != nil {
			return statuses, false, err
		}
		statuses = append(statuses, post)
	}
	if len(statuses) > perPage {
		return statuses[0:perPage], true, err
	}
	return statuses, false, err
}

func (s *Storage) LatestStatuses() ([]model.Status, error) {
	rows, err := s.db.Query(`
        select
		    statuses.id,
		    users.name,
		    statuses.content,
		    statuses.created_at,
	        statuses.face,
                statuses.number
	    from
		    users
	    inner join statuses
		    on users.status_id = statuses.id
	    order by statuses.created_at desc;
	`)
	if err != nil {
		return nil, err
	}
	var statuses []model.Status
	for rows.Next() {
		post, err := s.populateStatus(rows)
		if err != nil {
			return statuses, err
		}
		statuses = append(statuses, post)
	}
	return statuses, err
}

func (s *Storage) UpdateStatus(status model.Status) error {
	stmt, err := s.db.Prepare(`UPDATE statuses SET content = $1, face = $2, number = $3 WHERE id = $4 and author = $5;`)
	if err != nil {
		return err
	}
	_, err = stmt.Exec(status.Content, status.Face, status.Id, status.User, status.Number)
	return err
}

func (s *Storage) DeleteStatus(id int64, author string) error {
	ctx := context.Background()
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	var latestId int64
	if err := tx.QueryRowContext(ctx, `select status_id from users where name = $1`,
		author).Scan(&latestId); err != nil {
		tx.Rollback()
		return err
	}
	if _, err := tx.ExecContext(ctx, `UPDATE users set status_id=$1 where name=$2`, nil, author); err != nil {
		tx.Rollback()
		return err
	}
	if _, err := tx.ExecContext(ctx, `DELETE from statuses WHERE id = $1 and author = $2;`, id, author); err != nil {
		tx.Rollback()
		return err
	}
	if latestId == id {
		var newId int64
		if err := tx.QueryRowContext(ctx, `select id from statuses where author = $1 order by created_at desc limit 1;`,
			author).Scan(&newId); err != nil {
			if _, err := tx.ExecContext(ctx, `UPDATE users set status_id=$1 where name=$2`, nil, author); err != nil {
				tx.Rollback()
				return err
			}
			err = tx.Commit()
			return err
		}
		if _, err := tx.ExecContext(ctx, `UPDATE users set status_id=$1 where name=$2`, newId, author); err != nil {
			tx.Rollback()
			return err
		}
	} else {
		if _, err := tx.ExecContext(ctx, `UPDATE users set status_id=$1 where name=$2`, latestId, author); err != nil {
			tx.Rollback()
			return err
		}
	}

	err = tx.Commit()
	return err
}
