package plugin

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/hashicorp/go-hclog"

	"gitlab.com/gitlab-org/fleeting/fleeting/internal/plugin/proto"
	"gitlab.com/gitlab-org/fleeting/fleeting/provider"
)

var _ proto.InstanceGroupServer = (*instanceGroupGRPCServer)(nil)

type instanceGroupGRPCServer struct {
	Logger hclog.Logger
	Impl   provider.InstanceGroup

	proto.UnimplementedInstanceGroupServer
}

func (s *instanceGroupGRPCServer) Init(ctx context.Context, req *proto.InitRequest) (*proto.InitResponse, error) {
	if err := json.Unmarshal(req.Config, s.Impl); err != nil {
		return nil, fmt.Errorf("unmarshaling config: %w", err)
	}

	info, err := s.Impl.Init(ctx, s.Logger, provider.Settings{
		ConnectorConfig: provider.ConnectorConfig{
			OS:                   req.Settings.ConnectorConfig.Os,
			Arch:                 req.Settings.ConnectorConfig.Arch,
			Protocol:             provider.Protocol(req.Settings.ConnectorConfig.Protocol),
			Username:             req.Settings.ConnectorConfig.Username,
			Password:             req.Settings.ConnectorConfig.Password,
			Key:                  req.Settings.ConnectorConfig.Key,
			UseStaticCredentials: req.Settings.ConnectorConfig.UseStaticCredentials,
			Keepalive:            time.Duration(req.Settings.ConnectorConfig.Keepalive),
			Timeout:              time.Duration(req.Settings.ConnectorConfig.Timeout),
		},
	})
	if err != nil {
		return nil, err
	}

	return &proto.InitResponse{
		Id:        info.ID,
		MaxSize:   uint32(info.MaxSize),
		Version:   info.Version,
		BuildInfo: info.BuildInfo,
	}, nil
}

func (s *instanceGroupGRPCServer) Update(_ *proto.Empty, req proto.InstanceGroup_UpdateServer) error {
	return s.Impl.Update(req.Context(), func(instance string, state provider.State) {
		req.Send(&proto.UpdateResponse{
			Instance: instance,
			State:    proto.UpdateResponse_State(proto.UpdateResponse_State_value[string(state)]),
		})
	})
}

func (s *instanceGroupGRPCServer) Increase(ctx context.Context, req *proto.IncreaseRequest) (*proto.IncreaseResponse, error) {
	succeeded, err := s.Impl.Increase(ctx, int(req.N))

	return &proto.IncreaseResponse{Succeeded: uint32(succeeded)}, err
}

func (s *instanceGroupGRPCServer) Decrease(ctx context.Context, req *proto.DecreaseRequest) (*proto.DecreaseResponse, error) {
	succeeded, err := s.Impl.Decrease(ctx, req.Instances)

	return &proto.DecreaseResponse{Succeeded: succeeded}, err
}

func (s *instanceGroupGRPCServer) ConnectInfo(ctx context.Context, req *proto.ConnectInfoRequest) (*proto.ConnectInfoResponse, error) {
	info, err := s.Impl.ConnectInfo(ctx, req.Instance)
	if err != nil {
		return nil, err
	}

	return &proto.ConnectInfoResponse{
		Connector: &proto.ConnectorConfig{
			Os:                   info.ConnectorConfig.OS,
			Arch:                 info.ConnectorConfig.Arch,
			Protocol:             string(info.ConnectorConfig.Protocol),
			Username:             info.ConnectorConfig.Username,
			Password:             info.ConnectorConfig.Password,
			Key:                  info.ConnectorConfig.Key,
			UseStaticCredentials: info.ConnectorConfig.UseStaticCredentials,
			Keepalive:            int64(info.ConnectorConfig.Keepalive),
			Timeout:              int64(info.ConnectorConfig.Timeout),
		},
		ExternalAddr: info.ExternalAddr,
		InternalAddr: info.InternalAddr,
		Expires: func() int64 {
			if info.Expires == nil {
				return 0
			}
			return info.Expires.UTC().Unix()
		}(),
	}, nil
}

func (s *instanceGroupGRPCServer) Shutdown(ctx context.Context, req *proto.Empty) (*proto.Empty, error) {
	return empty, s.Impl.Shutdown(ctx)
}
