package project

import (
	"context"
	"errors"

	"go.uber.org/dig"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	projectv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/agent-sidecar/project/v1"
	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/rpcserver"
	"github.com/tidbcloud/aisupport/services/agent-sidecar/internal/app"
	projectapi "github.com/tidbcloud/aisupport/services/agent-sidecar/internal/services/project/api"
	projecterror "github.com/tidbcloud/aisupport/services/agent-sidecar/internal/services/project/projecterror"
)

type HandlerParam struct {
	dig.In

	Service projectapi.Service
}

type Handler struct {
	projectv1.UnimplementedProjectServiceServer

	service projectapi.Service
}

func NewHandler(param HandlerParam) app.HandlerResult {
	return app.HandlerResult{Handler: &Handler{service: param.Service}}
}

func (h *Handler) Name() string {
	return "ProjectService"
}

func (h *Handler) RegisterRPC(server *grpc.Server) error {
	projectv1.RegisterProjectServiceServer(server, h)
	return nil
}

func (h *Handler) ListProjects(ctx context.Context, req *projectv1.ListProjectsRequest) (*projectv1.ListProjectsResponse, error) {
	projects, err := h.service.ListProjects(ctx)
	if err != nil {
		return nil, grpcError(err)
	}
	return &projectv1.ListProjectsResponse{Projects: projects}, nil
}

func (h *Handler) CreateProject(ctx context.Context, req *projectv1.CreateProjectRequest) (*projectv1.CreateProjectResponse, error) {
	item, err := h.service.CreateProject(ctx, req.GetName())
	if err != nil {
		return nil, grpcError(err)
	}
	return &projectv1.CreateProjectResponse{Project: item}, nil
}

func (h *Handler) DeleteProject(ctx context.Context, req *projectv1.DeleteProjectRequest) (*projectv1.DeleteProjectResponse, error) {
	if err := h.service.DeleteProject(ctx, req.GetName(), req.GetConfirmName()); err != nil {
		return nil, grpcError(err)
	}
	return &projectv1.DeleteProjectResponse{}, nil
}

func (h *Handler) ListEntries(ctx context.Context, req *projectv1.ListEntriesRequest) (*projectv1.ListEntriesResponse, error) {
	entries, next, revision, err := h.service.ListEntries(ctx, req.GetProject(), req.GetPath(), int(req.GetPageSize()), req.GetCursor())
	if err != nil {
		return nil, grpcError(err)
	}
	return &projectv1.ListEntriesResponse{Entries: entries, NextCursor: next, DirectoryRevision: revision}, nil
}

func (h *Handler) GetFile(ctx context.Context, req *projectv1.GetFileRequest) (*projectv1.GetFileResponse, error) {
	file, err := h.service.GetFile(ctx, req.GetProject(), req.GetPath())
	if err != nil {
		return nil, grpcError(err)
	}
	return &projectv1.GetFileResponse{File: file}, nil
}

func (h *Handler) CreateEntry(ctx context.Context, req *projectv1.CreateEntryRequest) (*projectv1.CreateEntryResponse, error) {
	entry, err := h.service.CreateEntry(ctx, req.GetProject(), req.GetParentPath(), req.GetName(), req.GetType())
	if err != nil {
		return nil, grpcError(err)
	}
	return &projectv1.CreateEntryResponse{Entry: entry}, nil
}

func (h *Handler) UpdateFile(ctx context.Context, req *projectv1.UpdateFileRequest) (*projectv1.UpdateFileResponse, error) {
	entry, err := h.service.UpdateFile(ctx, req.GetProject(), req.GetPath(), req.GetContent(), req.GetExpectedRevision())
	if err != nil {
		return nil, grpcError(err)
	}
	return &projectv1.UpdateFileResponse{Entry: entry}, nil
}

func (h *Handler) MoveEntry(ctx context.Context, req *projectv1.MoveEntryRequest) (*projectv1.MoveEntryResponse, error) {
	entry, err := h.service.MoveEntry(ctx, req.GetProject(), req.GetSourcePath(), req.GetDestinationParentPath(), req.GetDestinationName(), req.GetExpectedRevision())
	if err != nil {
		return nil, grpcError(err)
	}
	return &projectv1.MoveEntryResponse{Entry: entry}, nil
}

func (h *Handler) DeleteEntry(ctx context.Context, req *projectv1.DeleteEntryRequest) (*projectv1.DeleteEntryResponse, error) {
	if err := h.service.DeleteEntry(ctx, req.GetProject(), req.GetPath(), req.GetRecursive(), req.GetExpectedRevision()); err != nil {
		return nil, grpcError(err)
	}
	return &projectv1.DeleteEntryResponse{}, nil
}

func grpcError(err error) error {
	code := codes.Internal
	switch {
	case errors.Is(err, projecterror.ErrInvalidArgument):
		code = codes.InvalidArgument
	case errors.Is(err, projecterror.ErrNotFound):
		code = codes.NotFound
	case errors.Is(err, projecterror.ErrAlreadyExists):
		code = codes.AlreadyExists
	case errors.Is(err, projecterror.ErrPermissionDenied):
		code = codes.PermissionDenied
	case errors.Is(err, projecterror.ErrConflict):
		code = codes.Aborted
	case errors.Is(err, projecterror.ErrResourceExhausted):
		code = codes.ResourceExhausted
	case errors.Is(err, projecterror.ErrFailedPrecondition):
		code = codes.FailedPrecondition
	case errors.Is(err, context.Canceled):
		code = codes.Canceled
	case errors.Is(err, context.DeadlineExceeded):
		code = codes.DeadlineExceeded
	}
	return status.Error(code, err.Error())
}

var _ projectv1.ProjectServiceServer = (*Handler)(nil)
var _ rpcserver.RPCHandler = (*Handler)(nil)
