package rest

import (
	"git.sch9.ru/new_gate/ms-tester/internal/models"
	"git.sch9.ru/new_gate/ms-tester/internal/tester"
	"git.sch9.ru/new_gate/ms-tester/pkg"
	testerv1 "git.sch9.ru/new_gate/ms-tester/proto/tester/v1"
	"github.com/gofiber/fiber/v2"
	"io"
)

type TesterHandlers struct {
	problemsUC tester.ProblemUseCase
	contestsUC tester.ContestUseCase
}

func NewTesterHandlers(problemsUC tester.ProblemUseCase, contestsUC tester.ContestUseCase) *TesterHandlers {
	return &TesterHandlers{
		problemsUC: problemsUC,
		contestsUC: contestsUC,
	}
}

func (h *TesterHandlers) ListContests(c *fiber.Ctx, params testerv1.ListContestsParams) error {
	contestsList, err := h.contestsUC.ListContests(c.Context(), models.ContestsFilter{
		Page:     params.Page,
		PageSize: params.PageSize,
	})
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	resp := testerv1.ListContestsResponse{
		Contests:   make([]testerv1.ContestsListItem, len(contestsList.Contests)),
		Pagination: P2P(contestsList.Pagination),
	}

	for i, contest := range contestsList.Contests {
		resp.Contests[i] = CLI2CLI(*contest)
	}

	return c.JSON(resp)
}

func (h *TesterHandlers) ListProblems(c *fiber.Ctx, params testerv1.ListProblemsParams) error {
	problemsList, err := h.problemsUC.ListProblems(c.Context(), models.ProblemsFilter{
		Page:     params.Page,
		PageSize: params.PageSize,
	})
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	resp := testerv1.ListProblemsResponse{
		Problems:   make([]testerv1.ProblemsListItem, len(problemsList.Problems)),
		Pagination: P2P(problemsList.Pagination),
	}

	for i, problem := range problemsList.Problems {
		resp.Problems[i] = PLI2PLI(*problem)
	}

	return c.JSON(resp)
}

func (h *TesterHandlers) CreateContest(c *fiber.Ctx) error {
	id, err := h.contestsUC.CreateContest(c.Context(), "Название контеста")
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.JSON(testerv1.CreateContestResponse{
		Id: id,
	})
}

func (h *TesterHandlers) DeleteContest(c *fiber.Ctx, id int32) error {
	err := h.contestsUC.DeleteContest(c.Context(), id)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.SendStatus(fiber.StatusOK)
}

func (h *TesterHandlers) GetContest(c *fiber.Ctx, id int32) error {
	contest, err := h.contestsUC.ReadContestById(c.Context(), id)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	//token, ok := c.Locals(TokenKey).(*models.JWT)
	//if !ok {
	//	return c.SendStatus(fiber.StatusUnauthorized)
	//}

	tasks, err := h.contestsUC.ReadTasks(c.Context(), id)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	resp := testerv1.GetContestResponse{
		Contest: C2C(*contest),
		Tasks: make([]struct {
			Solution testerv1.Solution      `json:"solution"`
			Task     testerv1.TasksListItem `json:"task"`
		}, len(tasks)),
	}

	for i, task := range tasks {
		resp.Tasks[i] = struct {
			Solution testerv1.Solution      `json:"solution"`
			Task     testerv1.TasksListItem `json:"task"`
		}{
			Solution: testerv1.Solution{},
			Task:     TLI2TLI(*task),
		}
	}

	return c.JSON(resp)
}

func (h *TesterHandlers) DeleteParticipant(c *fiber.Ctx, params testerv1.DeleteParticipantParams) error {
	err := h.contestsUC.DeleteParticipant(c.Context(), params.ParticipantId)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.SendStatus(fiber.StatusOK)
}

func (h *TesterHandlers) AddParticipant(c *fiber.Ctx, params testerv1.AddParticipantParams) error {
	id, err := h.contestsUC.AddParticipant(c.Context(), params.ContestId, params.UserId)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.JSON(testerv1.AddParticipantResponse{
		Id: id,
	})
}

func (h *TesterHandlers) DeleteTask(c *fiber.Ctx, id int32) error {
	err := h.contestsUC.DeleteTask(c.Context(), id)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.SendStatus(fiber.StatusOK)
}

func (h *TesterHandlers) AddTask(c *fiber.Ctx, params testerv1.AddTaskParams) error {
	id, err := h.contestsUC.AddTask(c.Context(), params.ContestId, params.ProblemId)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.JSON(testerv1.AddTaskResponse{
		Id: id,
	})
}

func (h *TesterHandlers) CreateProblem(c *fiber.Ctx) error {
	id, err := h.problemsUC.CreateProblem(c.Context(), "Название задачи")
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.JSON(testerv1.CreateProblemResponse{
		Id: id,
	})
}

func (h *TesterHandlers) DeleteProblem(c *fiber.Ctx, id int32) error {
	err := h.problemsUC.DeleteProblem(c.Context(), id)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.SendStatus(fiber.StatusOK)

}

func (h *TesterHandlers) GetProblem(c *fiber.Ctx, id int32) error {
	problem, err := h.problemsUC.ReadProblemById(c.Context(), id)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.JSON(
		testerv1.GetProblemResponse{Problem: *PR2PR(problem)},
	)
}

func (h *TesterHandlers) ListParticipants(c *fiber.Ctx, params testerv1.ListParticipantsParams) error {
	participantsList, err := h.contestsUC.ListParticipants(c.Context(), models.ParticipantsFilter{
		Page:      params.Page,
		PageSize:  params.PageSize,
		ContestId: params.ContestId,
	})
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	resp := testerv1.ListParticipantsResponse{
		Participants: make([]testerv1.ParticipantsListItem, len(participantsList.Participants)),
		Pagination:   P2P(participantsList.Pagination),
	}

	for i, participant := range participantsList.Participants {
		resp.Participants[i] = PTLI2PTLI(*participant)
	}

	return c.JSON(resp)
}

func (h *TesterHandlers) UpdateProblem(c *fiber.Ctx, id int32) error {
	var req testerv1.UpdateProblemRequest
	err := c.BodyParser(&req)
	if err != nil {
		return err
	}

	err = h.problemsUC.UpdateProblem(c.Context(), id, models.ProblemUpdate{
		Title:       req.Title,
		MemoryLimit: req.MemoryLimit,
		TimeLimit:   req.TimeLimit,

		Legend:       req.Legend,
		InputFormat:  req.InputFormat,
		OutputFormat: req.OutputFormat,
		Notes:        req.Notes,
		Scoring:      req.Scoring,
	})

	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.SendStatus(fiber.StatusOK)
}

func (h *TesterHandlers) UpdateContest(c *fiber.Ctx, id int32) error {
	var req testerv1.UpdateContestRequest
	err := c.BodyParser(&req)
	if err != nil {
		return err
	}

	err = h.contestsUC.UpdateContest(c.Context(), id, models.ContestUpdate{
		Title: req.Title,
	})
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.SendStatus(fiber.StatusOK)
}

func (h *TesterHandlers) UpdateParticipant(c *fiber.Ctx, params testerv1.UpdateParticipantParams) error {
	var req testerv1.UpdateParticipantRequest
	err := c.BodyParser(&req)
	if err != nil {
		return err
	}

	err = h.contestsUC.UpdateParticipant(c.Context(), params.ParticipantId, models.ParticipantUpdate{
		Name: req.Name,
	})
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.SendStatus(fiber.StatusOK)
}

func (h *TesterHandlers) ListSolutions(c *fiber.Ctx, params testerv1.ListSolutionsParams) error {
	solutionsList, err := h.contestsUC.ListSolutions(c.Context(), models.SolutionsFilter{
		ContestId:     params.ContestId,
		Page:          params.Page,
		PageSize:      params.PageSize,
		ParticipantId: params.ParticipantId,
		TaskId:        params.TaskId,
		Language:      params.Language,
		Order:         params.Order,
		State:         params.State,
	})
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	resp := testerv1.ListSolutionsResponse{
		Solutions:  make([]testerv1.SolutionsListItem, len(solutionsList.Solutions)),
		Pagination: P2P(solutionsList.Pagination),
	}

	for i, solution := range solutionsList.Solutions {
		resp.Solutions[i] = SLI2SLI(*solution)
	}

	return c.JSON(resp)
}

const (
	maxSolutionSize int64 = 10 * 1024 * 1024
)

func (h *TesterHandlers) CreateSolution(c *fiber.Ctx, params testerv1.CreateSolutionParams) error {
	s, err := c.FormFile("solution")
	if err != nil {
		return err
	}

	if s.Size == 0 || s.Size > maxSolutionSize {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	f, err := s.Open()
	if err != nil {
		return err
	}
	defer f.Close()

	b, err := io.ReadAll(f)
	if err != nil {
		return err
	}

	id, err := h.contestsUC.CreateSolution(c.Context(), &models.SolutionCreation{
		TaskId:        params.TaskId,
		ParticipantId: 1,
		Language:      params.Language,
		Penalty:       0,
		Solution:      string(b),
	})
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.JSON(testerv1.CreateSolutionResponse{
		Id: id,
	})
}

func (h *TesterHandlers) GetSolution(c *fiber.Ctx, id int32) error {
	solution, err := h.contestsUC.ReadSolution(c.Context(), id)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	return c.JSON(
		testerv1.GetSolutionResponse{Solution: S2S(*solution)},
	)
}

func (h *TesterHandlers) GetTask(c *fiber.Ctx, id int32) error {
	contest, err := h.contestsUC.ReadContestById(c.Context(), id)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	tasks, err := h.contestsUC.ReadTasks(c.Context(), id)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	t, err := h.contestsUC.ReadTask(c.Context(), id)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	resp := testerv1.GetTaskResponse{
		Contest: C2C(*contest),
		Tasks:   make([]testerv1.TasksListItem, len(tasks)),
		Task:    *T2T(t),
	}

	for i, task := range tasks {
		resp.Tasks[i] = TLI2TLI(*task)
	}

	return c.JSON(resp)
}

func (h *TesterHandlers) GetMonitor(c *fiber.Ctx, params testerv1.GetMonitorParams) error {
	contest, err := h.contestsUC.ReadContestById(c.Context(), params.ContestId)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	monitor, err := h.contestsUC.ReadMonitor(c.Context(), params.ContestId)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	tasks, err := h.contestsUC.ReadTasks(c.Context(), params.ContestId)
	if err != nil {
		return c.SendStatus(pkg.ToREST(err))
	}

	resp := testerv1.GetMonitorResponse{
		Contest:           C2C(*contest),
		Tasks:             make([]testerv1.TasksListItem, len(tasks)),
		Participants:      make([]testerv1.ParticipantsStat, len(monitor.Participants)),
		SummaryPerProblem: make([]testerv1.ProblemStatSummary, len(monitor.Summary)),
	}

	for i, participant := range monitor.Participants {
		resp.Participants[i] = testerv1.ParticipantsStat{
			Id:             participant.Id,
			Name:           participant.Name,
			PenaltyInTotal: participant.PenaltyInTotal,
			Solutions:      make([]testerv1.SolutionsListItem, len(participant.Solutions)),
			SolvedInTotal:  participant.SolvedInTotal,
		}

		for j, solution := range participant.Solutions {
			resp.Participants[i].Solutions[j] = SLI2SLI(*solution)
		}
	}

	for i, problem := range monitor.Summary {
		resp.SummaryPerProblem[i] = testerv1.ProblemStatSummary{
			Id:      problem.Id,
			Success: problem.Success,
			Total:   problem.Total,
		}
	}

	for i, task := range tasks {
		resp.Tasks[i] = TLI2TLI(*task)
	}

	return c.JSON(resp)
}

func P2P(p models.Pagination) testerv1.Pagination {
	return testerv1.Pagination{
		Page:  p.Page,
		Total: p.Total,
	}
}

func C2C(c models.Contest) testerv1.Contest {
	return testerv1.Contest{
		Id:        c.Id,
		Title:     c.Title,
		CreatedAt: c.CreatedAt,
		UpdatedAt: c.UpdatedAt,
	}
}

func CLI2CLI(c models.ContestsListItem) testerv1.ContestsListItem {
	return testerv1.ContestsListItem{
		Id:        c.Id,
		Title:     c.Title,
		CreatedAt: c.CreatedAt,
		UpdatedAt: c.UpdatedAt,
	}
}

func PLI2PLI(p models.ProblemsListItem) testerv1.ProblemsListItem {
	return testerv1.ProblemsListItem{
		Id:          p.Id,
		Title:       p.Title,
		MemoryLimit: p.MemoryLimit,
		TimeLimit:   p.TimeLimit,
		CreatedAt:   p.CreatedAt,
		UpdatedAt:   p.UpdatedAt,
	}
}

func TLI2TLI(t models.TasksListItem) testerv1.TasksListItem {
	return testerv1.TasksListItem{
		Id:          t.Id,
		Position:    t.Position,
		Title:       t.Title,
		MemoryLimit: t.MemoryLimit,
		ProblemId:   t.ProblemId,
		TimeLimit:   t.TimeLimit,
		CreatedAt:   t.CreatedAt,
		UpdatedAt:   t.UpdatedAt,
	}
}

func T2T(t *models.Task) *testerv1.Task {
	return &testerv1.Task{
		Id:          t.Id,
		Title:       t.Title,
		MemoryLimit: t.MemoryLimit,
		TimeLimit:   t.TimeLimit,

		InputFormatHtml:  t.InputFormatHtml,
		LegendHtml:       t.LegendHtml,
		NotesHtml:        t.NotesHtml,
		OutputFormatHtml: t.OutputFormatHtml,
		Position:         t.Position,
		ScoringHtml:      t.ScoringHtml,

		CreatedAt: t.CreatedAt,
		UpdatedAt: t.UpdatedAt,
	}
}

func PR2PR(p *models.Problem) *testerv1.Problem {
	return &testerv1.Problem{
		Id:          p.Id,
		Title:       p.Title,
		TimeLimit:   p.TimeLimit,
		MemoryLimit: p.MemoryLimit,

		Legend:       p.Legend,
		InputFormat:  p.InputFormat,
		OutputFormat: p.OutputFormat,
		Notes:        p.Notes,
		Scoring:      p.Scoring,

		LegendHtml:       p.LegendHtml,
		InputFormatHtml:  p.InputFormatHtml,
		OutputFormatHtml: p.OutputFormatHtml,
		NotesHtml:        p.NotesHtml,
		ScoringHtml:      p.ScoringHtml,

		CreatedAt: p.CreatedAt,
		UpdatedAt: p.UpdatedAt,
	}
}

func PTLI2PTLI(p models.ParticipantsListItem) testerv1.ParticipantsListItem {
	return testerv1.ParticipantsListItem{
		Id:        p.Id,
		UserId:    p.UserId,
		Name:      p.Name,
		CreatedAt: p.CreatedAt,
		UpdatedAt: p.UpdatedAt,
	}
}

func SLI2SLI(s models.SolutionsListItem) testerv1.SolutionsListItem {
	return testerv1.SolutionsListItem{
		ContestId:     s.ContestId,
		CreatedAt:     s.CreatedAt,
		Id:            s.Id,
		Language:      s.Language,
		ParticipantId: s.ParticipantId,
		Penalty:       s.Penalty,
		Score:         s.Score,
		State:         s.State,
		TaskId:        s.TaskId,
		TotalScore:    s.TotalScore,
		UpdatedAt:     s.UpdatedAt,
	}
}

func S2S(s models.Solution) testerv1.Solution {
	return testerv1.Solution{
		Id:            s.Id,
		TaskId:        s.TaskId,
		ParticipantId: s.ParticipantId,
		Solution:      s.Solution,
		State:         s.State,
		Score:         s.Score,
		Penalty:       s.Penalty,
		TotalScore:    s.TotalScore,
		Language:      s.Language,
		CreatedAt:     s.CreatedAt,
		UpdatedAt:     s.UpdatedAt,
	}
}