package transport

import (
	"context"
	"git.sch9.ru/new_gate/ms-tester/internal/lib"
	"git.sch9.ru/new_gate/ms-tester/internal/models"
	problemv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/problem/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"io"
	"os"
)

func (s *TesterServer) CreateProblem(server problemv1.ProblemService_CreateProblemServer) error {
	ctx := server.Context()

	if err := s.problemService.CanCreateProblem(ctx); err != nil {
		return err
	}

	req, err := server.Recv() // receive problem
	if err != nil {
		return lib.TransportError(err, lib.ErrBadInput, "can't receive problem")
	}
	problem := req.GetProblem()
	if problem == nil {
		return lib.TransportError(nil, lib.ErrBadInput, "empty problem")
	}

	p := &models.Problem{
		Name:        lib.AsStringP(problem.Name),
		Description: lib.AsStringP(problem.Description),
		TimeLimit:   lib.AsInt32P(problem.TimeLimit),
		MemoryLimit: lib.AsInt32P(problem.MemoryLimit),
	}

	ch := readChunks(ctx, server)

	err = writeChunks(ctx, ch) // temp stub
	if err != nil {
		return err // FIXME
	}

	id, err := s.problemService.CreateProblem(ctx, p) // FIXME
	if err != nil {
		return err
	}

	err = server.SendAndClose(&problemv1.CreateProblemResponse{
		Id: id,
	})
	if err != nil {
		return lib.TransportError(err, lib.ErrBadInput, "can't send response")
	}

	return nil
}

func writeChunks(ctx context.Context, chunks <-chan []byte) error {
	// use s3
	// FIXME: use ctx?
	f, err := os.Create("out.txt") // FIXME: uuidv4 as initial temp name?
	if err != nil {
		return err
	}
	defer f.Close()

	var off int64 = 0
	for chunk := range chunks {
		_, err = f.WriteAt(chunk, off)
		if err != nil {
			return err
		}

		off += int64(len(chunk))
	}

	// TODO: rename file to its hash
	return nil
}

func readChunks(ctx context.Context, server problemv1.ProblemService_CreateProblemServer) <-chan []byte {
	ch := make(chan []byte)

	go func() {
		defer close(ch)
		for {
			select {
			case <-ctx.Done():
				return // FIXME
			default:
				req, err := server.Recv()
				if err != nil {
					if err == io.EOF {
						return // FIXME
					}
					if status.Code(err) == codes.Canceled {
						return // FIXME
					}
					continue
				}

				test := req.GetTest()
				if test == nil {
					return // FIXME
				}

				ch <- test.Chunk
			}
		}
	}()
	return ch
}

func (s *TesterServer) ReadProblem(ctx context.Context, req *problemv1.ReadProblemRequest) (*problemv1.ReadProblemResponse, error) {
	problem, err := s.problemService.ReadProblemById(ctx, req.GetId())
	if err != nil {
		return nil, err
	}
	return &problemv1.ReadProblemResponse{
		Problem: &problemv1.ReadProblemResponse_Problem{
			Id:          *problem.Id,
			Name:        *problem.Name,
			Description: *problem.Description,
			TimeLimit:   *problem.TimeLimit,
			MemoryLimit: *problem.MemoryLimit,
			CreatedAt:   AsTimestampP(problem.CreatedAt),
			UpdatedAt:   AsTimestampP(problem.UpdatedAt),
		},
	}, nil
}

//func (s *TesterServer) UpdateProblem(ctx context.Context, req *problemv1.UpdateProblemRequest) (*emptypb.Empty, error) {
//	problem := req.GetProblem()
//	if problem == nil {
//		return nil, status.Errorf(codes.Unknown, "") // FIXME
//	}
//	err := s.problemService.UpdateProblem(
//		ctx,
//		&models.Problem{
//			Id:          lib.AsInt32P(problem.Id),
//			Name:        problem.Name,
//			Description: problem.Description,
//			TimeLimit:   problem.TimeLimit,
//			MemoryLimit: problem.MemoryLimit,
//		},
//	)
//	if err != nil {
//		return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
//	}
//
//	return &emptypb.Empty{}, nil
//}

func (s *TesterServer) DeleteProblem(ctx context.Context, req *problemv1.DeleteProblemRequest) (*emptypb.Empty, error) {
	err := s.problemService.DeleteProblem(ctx, req.GetId())
	if err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}
