package graphql

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.41

import (
	"context"
	"fmt"
	"log"
	"strconv"

	"github.com/programme-lv/backend/internal/database"
	"github.com/programme-lv/backend/internal/services/objects"
	"github.com/programme-lv/backend/internal/services/tasks"
	"golang.org/x/exp/slog"
)

// CreateTask is the resolver for the createTask field.
func (r *mutationResolver) CreateTask(ctx context.Context, name string, code string) (*Task, error) {
	if len(name) == 0 || len(code) == 0 {
		return nil, fmt.Errorf("name and code must not be empty")
	}

	nameMaxLength := 50
	codeMaxLength := 20

	if len(name) > nameMaxLength {
		return nil, fmt.Errorf("name must be at most %d characters long", nameMaxLength)
	}

	if len(code) > codeMaxLength {
		return nil, fmt.Errorf("code must be at most %d characters long", codeMaxLength)
	}

	// code can contain only lowercase latin letters and digits
	for _, c := range code {
		if !((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
			return nil, fmt.Errorf("task code can contain only lowercase latin letters and digits")
		}
	}

	requestLogger := r.Logger.With(slog.String("request_type", "create_task"),
		slog.String("name", name), slog.String("code", code))

	requestLogger.Info("received create task request")

	user, err := r.GetUserFromContext(ctx)
	if err != nil {
		requestLogger.Error("failed to get user from context", slog.String("error", err.Error()))
		return nil, err
	}

	if !user.IsAdmin {
		requestLogger.Error("only admins can create tasks")
		return nil, fmt.Errorf("only admins can create tasks")
	}

	requestLogger = requestLogger.With(slog.Int64("user_id", user.ID))
	requestLogger.Info("got user from context")

	publishedVersionIdsStmt := "SELECT DISTINCT published_version_id FROM tasks WHERE published_version_id IS NOT NULL"
	stmt := "SELECT COUNT(*) from task_versions WHERE (short_code = $1 OR full_name = $2) AND task_versions.id IN (%s);"
	stmt = fmt.Sprintf(stmt, publishedVersionIdsStmt)
	var count int
	err = r.PostgresDB.Get(&count, stmt, code, name)
	if err != nil {
		requestLogger.Error("failed to check if task with the same name or code already exists", slog.String("error", err.Error()))
		return nil, err
	}

	if count > 0 {
		return nil, fmt.Errorf("task with the same name or code is already published")
	}

	t, err := r.PostgresDB.Beginx()
	if err != nil {
		requestLogger.Error("failed to begin transaction", slog.String("error", err.Error()))
		return nil, err
	}

	stmt = "INSERT INTO tasks (created_by_id, relevant_version_id) VALUES ($1, $2) RETURNING id"
	var taskId int64
	err = t.QueryRow(stmt, user.ID, nil).Scan(&taskId)
	if err != nil {
		t.Rollback()
		requestLogger.Error("failed to insert task", slog.String("error", err.Error()))
		return nil, err
	}

	requestLogger = requestLogger.With(slog.Int64("task_id", taskId))

	var task database.Task
	err = t.Get(&task, "SELECT * FROM tasks WHERE id = $1", taskId)
	if err != nil {
		t.Rollback()
		requestLogger.Error("failed to get task", slog.String("error", err.Error()))
		return nil, err
	}

	requestLogger.Info("created task successfully")

	DefaultTimeLimitMs := 1000
	DefaultMemoryLimitKb := 256000

	stmt = `INSERT INTO task_versions
    (task_id, short_code, full_name, time_lim_ms, mem_lim_kb, testing_type_id)
    VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`

	var versionId int64
	err = t.QueryRow(stmt, task.ID, code, name, DefaultTimeLimitMs, DefaultMemoryLimitKb, "simple").Scan(&versionId)
	if err != nil {
		t.Rollback()
		requestLogger.Error("failed to insert task version", slog.String("error", err.Error()))
		return nil, err
	}

	requestLogger = requestLogger.With(slog.Int64("version_id", versionId))

	var version database.TaskVersion
	err = t.Get(&version, "SELECT * FROM task_versions WHERE id = $1", versionId)
	if err != nil {
		t.Rollback()
		requestLogger.Error("failed to get task version", slog.String("error", err.Error()))
		return nil, err
	}

	requestLogger.Info("inserted task version successfully")

	_, err = t.Exec("UPDATE tasks SET relevant_version_id = $1 WHERE id = $2", versionId, task.ID)
	if err != nil {
		t.Rollback()
		requestLogger.Error("failed to update task relevant version", slog.String("error", err.Error()))
		return nil, err
	}

	requestLogger.Info("updated task relevant version successfully")

	stmt = `INSERT INTO public.markdown_statements(
	story, input, output, notes, scoring, task_version_id)
	VALUES ($1, $2, $3, $4, $5, $6)`

	_, err = t.Exec(stmt, "", "", "", "", "", versionId)
	if err != nil {
		t.Rollback()
		requestLogger.Error("failed to insert markdown statement", slog.String("error", err.Error()))
		return nil, err
	}

	err = t.Commit()
	if err != nil {
		requestLogger.Error("failed to commit transaction", slog.String("error", err.Error()))
		return nil, err
	}

	requestLogger.Info("committed transaction successfully")

	description := Description{
		ID:       "0",
		Story:    "",
		Input:    "",
		Output:   "",
		Examples: nil,
		Notes:    nil,
	}

	constraints := Constraints{
		TimeLimitMs:   version.TimeLimMs,
		MemoryLimitKb: version.MemLimKb,
	}

	metadata := Metadata{
		Authors: []string{},
		Origin:  nil,
	}

	updatedAt := version.UpdatedAt
	if updatedAt == nil {
		updatedAt = &task.CreatedAt
	}

	// TODO: add default author

	return &Task{
		ID:          fmt.Sprintf("%d", task.ID),
		Code:        version.ShortCode,
		Name:        version.FullName,
		Description: &description,
		Constraints: &constraints,
		Metadata:    &metadata,
		CreatedAt:   task.CreatedAt.UTC().String(),
		UpdatedAt:   updatedAt.UTC().String(),
	}, nil
}

// UpdateTaskMetadata is the resolver for the updateTaskMetadata field.
func (r *mutationResolver) UpdateTaskMetadata(ctx context.Context, id string, authors []string, origin *string) (*Task, error) {
	panic(fmt.Errorf("not implemented: UpdateTaskMetadata - updateTaskMetadata"))
}

// UpdateTaskDescription is the resolver for the updateTaskDescription field.
func (r *mutationResolver) UpdateTaskDescription(ctx context.Context, id string, code *string, name *string, story *string, input *string, output *string, notes *string) (*Task, error) {
	panic(fmt.Errorf("not implemented: UpdateTaskDescription - updateTaskDescription"))
}

// UpdateTaskExamples is the resolver for the updateTaskExamples field.
func (r *mutationResolver) UpdateTaskExamples(ctx context.Context, id string, inputs []string, outputs []string) (*Task, error) {
	panic(fmt.Errorf("not implemented: UpdateTaskExamples - updateTaskExamples"))
}

// UpdateTaskConstraints is the resolver for the updateTaskConstraints field.
func (r *mutationResolver) UpdateTaskConstraints(ctx context.Context, id string, timeLimitMs *int, memoryLimitKb *int) (*Task, error) {
	panic(fmt.Errorf("not implemented: UpdateTaskConstraints - updateTaskConstraints"))
}

// PublishTask is the resolver for the publishTask field.
func (r *mutationResolver) PublishTask(ctx context.Context, id string) (*Task, error) {
	panic(fmt.Errorf("not implemented: PublishTask - publishTask"))
}

// DeleteTask is the resolver for the deleteTask field.
func (r *mutationResolver) DeleteTask(ctx context.Context, id string) (*Task, error) {
	panic(fmt.Errorf("not implemented: DeleteTask - deleteTask"))
}

// ListPublishedTasks is the resolver for the listPublishedTasks field.
func (r *queryResolver) ListPublishedTasks(ctx context.Context) ([]*Task, error) {
	publishedTaskVersions, err := tasks.ListPublishedTaskVersions(r.PostgresDB)
	if err != nil {
		log.Println(err)
		return nil, err
	}

	solvedTaskSet := make(map[int64]bool)
	isLoggedIn := false

	// check if user logged in
	user, err := r.GetUserFromContext(ctx)
	if err == nil {
		isLoggedIn = true

		solvedTasks, err := tasks.ListSolvedTasksByUserID(r.PostgresDB, user.ID)
		if err != nil {
			log.Println(err)
			return nil, err
		}

		for _, solvedTask := range solvedTasks {
			solvedTaskSet[solvedTask] = true
		}
	}

	var result []*Task
	for _, task := range publishedTaskVersions {
		result = append(result, internalTaskVersionToGraphQLTask(task))
		if isLoggedIn {
			isSolved := false
			if val, ok := solvedTaskSet[task.TaskID]; ok {
				isSolved = val
			}
			result[len(result)-1].Solved = &isSolved
		}
	}

	return result, nil
}

// GetPublishedTaskVersionByCode is the resolver for the getPublishedTaskVersionByCode field.
func (r *queryResolver) GetPublishedTaskVersionByCode(ctx context.Context, code string) (*Task, error) {
	task, err := tasks.GetPublishedTaskVersionByCode(r.PostgresDB, code)
	if err != nil {
		log.Println(err)
		return nil, err
	}

	return internalTaskVersionToGraphQLTask(*task), nil
}

func internalTaskVersionToGraphQLTask(task objects.TaskVersion) *Task {
	var examples []*Example
	for _, example := range task.Description.Examples {
		examples = append(examples, &Example{
			ID:     strconv.FormatInt(example.ID, 10),
			Input:  example.Input,
			Answer: example.Answer,
		})
	}

	return &Task{
		ID:   strconv.FormatInt(task.TaskID, 10),
		Code: task.Code,
		Name: task.Name,
		Description: &Description{
			ID:       strconv.FormatInt(task.Description.ID, 10),
			Story:    task.Description.Story,
			Input:    task.Description.Input,
			Output:   task.Description.Output,
			Examples: examples,
			Notes:    task.Description.Notes,
		},
		Constraints: &Constraints{
			TimeLimitMs:   int(task.TimeLimitMs),
			MemoryLimitKb: int(task.MemoryLimitKb),
		},
		Metadata: &Metadata{
			Authors: []string{},
			Origin:  new(string),
		},
		CreatedAt: task.CreatedAt.UTC().String(),
		UpdatedAt: task.CreatedAt.UTC().String(),
	}
}

// ListEditableTasks is the resolver for the listEditableTasks field.
func (r *queryResolver) ListEditableTasks(ctx context.Context) ([]*Task, error) {
	panic(fmt.Errorf("not implemented: ListEditableTasks - listEditableTasks"))
}

// GetCurrentTaskVersionByID is the resolver for the getCurrentTaskVersionById field.
func (r *queryResolver) GetCurrentTaskVersionByID(ctx context.Context, id string) (*Task, error) {
	panic(fmt.Errorf("not implemented: GetCurrentTaskVersionByID - getCurrentTaskVersionById"))
}
