package repository

import (
	"context"
	"git.sch9.ru/new_gate/ms-tester/internal/models"
	"git.sch9.ru/new_gate/ms-tester/internal/tester"
	"github.com/jmoiron/sqlx"
)

type ProblemRepository struct {
	_db *sqlx.DB
}

func NewProblemRepository(db *sqlx.DB) *ProblemRepository {
	return &ProblemRepository{
		_db: db,
	}
}

func (r *ProblemRepository) BeginTx(ctx context.Context) (tester.Tx, error) {
	tx, err := r._db.BeginTxx(ctx, nil)
	if err != nil {
		return nil, err
	}

	return tx, nil
}

func (r *ProblemRepository) DB() tester.Querier {
	return r._db
}

const createProblemQuery = "INSERT INTO problems (title) VALUES (?) RETURNING id"

func (r *ProblemRepository) CreateProblem(ctx context.Context, q tester.Querier, title string) (int32, error) {
	const op = "ProblemRepository.CreateProblem"

	query := q.Rebind(createProblemQuery)
	rows, err := q.QueryxContext(ctx, query, title)
	if err != nil {
		return 0, handlePgErr(err, op)
	}

	defer rows.Close()
	var id int32
	rows.Next()
	err = rows.Scan(&id)
	if err != nil {
		return 0, handlePgErr(err, op)
	}

	return id, nil
}

const readProblemQuery = "SELECT * from problems WHERE id=? LIMIT 1"

func (r *ProblemRepository) ReadProblemById(ctx context.Context, q tester.Querier, id int32) (*models.Problem, error) {
	const op = "ProblemRepository.ReadProblemById"

	var problem models.Problem
	query := q.Rebind(readProblemQuery)
	err := q.GetContext(ctx, &problem, query, id)
	if err != nil {
		return nil, handlePgErr(err, op)
	}

	return &problem, nil
}

const deleteProblemQuery = "DELETE FROM problems WHERE id=?"

func (r *ProblemRepository) DeleteProblem(ctx context.Context, q tester.Querier, id int32) error {
	const op = "ProblemRepository.DeleteProblem"

	query := q.Rebind(deleteProblemQuery)
	_, err := q.ExecContext(ctx, query, id)
	if err != nil {
		return handlePgErr(err, op)
	}

	return nil
}

const (
	ListProblemsQuery = `SELECT id, title, time_limit, memory_limit, created_at, updated_at
FROM problems
LIMIT ? OFFSET ?`
	CountProblemsQuery = "SELECT COUNT(*) FROM problems"
)

func (r *ProblemRepository) ListProblems(ctx context.Context, q tester.Querier, filter models.ProblemsFilter) (*models.ProblemsList, error) {
	const op = "ContestRepository.ListProblems"

	if filter.PageSize > 20 || filter.PageSize < 1 {
		filter.PageSize = 1
	}

	var problems []*models.ProblemsListItem
	query := q.Rebind(ListProblemsQuery)
	err := q.SelectContext(ctx, &problems, query, filter.PageSize, filter.Offset())
	if err != nil {
		return nil, handlePgErr(err, op)
	}

	query = q.Rebind(CountProblemsQuery)

	var count int32
	err = q.GetContext(ctx, &count, query)
	if err != nil {
		return nil, handlePgErr(err, op)
	}

	return &models.ProblemsList{
		Problems: problems,
		Pagination: models.Pagination{
			Total: models.Total(count, filter.PageSize),
			Page:  filter.Page,
		},
	}, nil
}

const (
	UpdateProblemQuery = `UPDATE problems
SET title              = COALESCE(?, title),
    time_limit         = COALESCE(?, time_limit),
    memory_limit       = COALESCE(?, memory_limit),

    legend             = COALESCE(?, legend),
    input_format       = COALESCE(?, input_format),
    output_format      = COALESCE(?, output_format),
    notes              = COALESCE(?, notes),
    scoring            = COALESCE(?, scoring),

    legend_html        = COALESCE(?, legend_html),
    input_format_html  = COALESCE(?, input_format_html),
    output_format_html = COALESCE(?, output_format_html),
    notes_html         = COALESCE(?, notes_html),
    scoring_html       = COALESCE(?, scoring_html)

WHERE id=?`
)

func (r *ProblemRepository) UpdateProblem(ctx context.Context, q tester.Querier, id int32, problem models.ProblemUpdate) error {
	const op = "ProblemRepository.UpdateProblem"

	query := q.Rebind(UpdateProblemQuery)
	_, err := q.ExecContext(ctx, query,
		problem.Title,
		problem.TimeLimit,
		problem.MemoryLimit,

		problem.Legend,
		problem.InputFormat,
		problem.OutputFormat,
		problem.Notes,
		problem.Scoring,

		problem.LegendHtml,
		problem.InputFormatHtml,
		problem.OutputFormatHtml,
		problem.NotesHtml,
		problem.ScoringHtml,

		id,
	)
	if err != nil {
		return handlePgErr(err, op)
	}

	return nil
}