package server

import (
	"context"
	"fmt"
	"net"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	log "github.com/sirupsen/logrus"

	cpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkelement"
	"code.fbi.h-da.de/danet/gosdn/controller/metrics"
	"code.fbi.h-da.de/danet/gosdn/controller/store"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/peer"
	"google.golang.org/grpc/status"
)

// CsbiServer represents a csbi server.
type CsbiServer struct {
	cpb.UnimplementedCsbiServiceServer
	pndStore networkdomain.PndStore
}

// NewCsbiServer receives a pndStore and returns a new csbiServer.
func NewCsbiServer(pndStore networkdomain.PndStore) *CsbiServer {
	return &CsbiServer{
		pndStore: pndStore,
	}
}

// Hello is used for tests.
func (s CsbiServer) Hello(ctx context.Context, syn *cpb.Syn) (*cpb.Ack, error) {
	labels := prometheus.Labels{"service": "csbi", "rpc": "hello"}
	start := metrics.StartHook(labels, grpcRequestsTotal)
	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
	ch, err := s.pndStore.PendingChannels(store.FromString(syn.Id))
	if err != nil {
		return nil, handleRPCError(labels, err)
	}
	p, ok := peer.FromContext(ctx)
	if !ok {
		e := fmt.Errorf("no peer information in context %v", ctx)
		return nil, handleRPCError(labels, e)
	}
	csbiAddress, err := removePort(p.Addr)
	if err != nil {
		log.Error(err)
		return nil, status.Errorf(codes.Aborted, "%v", err)
	}
	ch <- networkelement.Details{
		ID:      syn.Id,
		Address: net.JoinHostPort(csbiAddress, syn.Address),
	}
	details := <-ch
	log.Infof("ack to csbi %v", syn.Id)
	log.WithField("transport option", details.TransportOption).Debug("csbi ack transport options")
	return &cpb.Ack{
		Timestamp:       time.Now().UnixNano(),
		TransportOption: details.TransportOption,
	}, nil
}

func removePort(ip net.Addr) (string, error) {
	addr, ok := ip.(*net.TCPAddr)
	if !ok {
		return "", fmt.Errorf("invalid type assertion")
	}
	return addr.IP.String(), nil
}
