package server

import (
	"context"
	"time"

	cpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
	ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
	"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/interfaces/plugin"
	"code.fbi.h-da.de/danet/gosdn/controller/metrics"
	"code.fbi.h-da.de/danet/gosdn/controller/nucleus"
	"code.fbi.h-da.de/danet/gosdn/controller/store"
	"github.com/google/uuid"
	"github.com/prometheus/client_golang/prometheus"
)

// PndServer represents a core server.
type PndServer struct {
	ppb.UnimplementedPndServiceServer
	pndService    networkdomain.Service
	pluginService plugin.Service
	csbiClient    cpb.CsbiServiceClient
	pndCallbackFn func(uuid.UUID, chan networkelement.Details)
}

// NewPndServer receives a pndStore and returns a new PndServer.
func NewPndServer(pndService networkdomain.Service, pluginService plugin.Service, pndCallbackFn func(uuid.UUID, chan networkelement.Details), csbiClient cpb.CsbiServiceClient) *PndServer {
	return &PndServer{
		pndService:    pndService,
		pluginService: pluginService,
		pndCallbackFn: pndCallbackFn,
		csbiClient:    csbiClient,
	}
}

// GetPnd returns a existing pnd.
func (s PndServer) GetPnd(ctx context.Context, request *ppb.GetPndRequest) (*ppb.GetPndResponse, error) {
	labels := prometheus.Labels{"service": "core", "rpc": "get"}
	start := metrics.StartHook(labels, grpcRequestsTotal)
	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)

	pndID, err := uuid.Parse(request.Pid)
	if err != nil {
		return nil, handleRPCError(labels, err)
	}

	storedPnd, err := s.pndService.Get(store.Query{ID: pndID})
	if err != nil {
		return nil, err
	}

	pnd := &ppb.PrincipalNetworkDomain{
		Id:          storedPnd.ID().String(),
		Name:        storedPnd.GetName(),
		Description: storedPnd.GetDescription(),
	}

	return &ppb.GetPndResponse{
		Timestamp: time.Now().UnixNano(),
		Pnd:       pnd,
	}, nil
}

// GetPndList returns all existing pnds.
func (s PndServer) GetPndList(ctx context.Context, request *ppb.GetPndListRequest) (*ppb.GetPndListResponse, error) {
	labels := prometheus.Labels{"service": "core", "rpc": "get"}
	start := metrics.StartHook(labels, grpcRequestsTotal)
	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)

	pndList, err := s.pndService.GetAll()
	if err != nil {
		return nil, err
	}

	pnds := make([]*ppb.PrincipalNetworkDomain, len(pndList))
	for i, pnd := range pndList {
		pnds[i] = &ppb.PrincipalNetworkDomain{
			Id:          pnd.ID().String(),
			Name:        pnd.GetName(),
			Description: pnd.GetDescription(),
		}
	}
	return &ppb.GetPndListResponse{
		Timestamp: time.Now().UnixNano(),
		Pnd:       pnds,
	}, nil
}

// CreatePndList creates a pnd list.
func (s PndServer) CreatePndList(ctx context.Context, request *ppb.CreatePndListRequest) (*ppb.CreatePndListResponse, error) {
	labels := prometheus.Labels{"service": "core", "rpc": "set"}
	start := metrics.StartHook(labels, grpcRequestsTotal)
	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
	for _, r := range request.Pnd {
		pnd := nucleus.NewPND(uuid.New(), r.Name, r.Description)

		if err := s.pndService.Add(pnd); err != nil {
			return nil, handleRPCError(labels, err)
		}
	}
	return &ppb.CreatePndListResponse{
		Timestamp: time.Now().UnixNano(),
		Status:    ppb.Status_STATUS_OK,
	}, nil
}

// DeletePnd deletes an existing pnd.
func (s PndServer) DeletePnd(ctx context.Context, request *ppb.DeletePndRequest) (*ppb.DeletePndResponse, error) {
	labels := prometheus.Labels{"service": "core", "rpc": "set"}
	start := metrics.StartHook(labels, grpcRequestsTotal)
	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)

	pndID, err := uuid.Parse(request.Pid)
	if err != nil {
		return nil, handleRPCError(labels, err)
	}

	pnd, err := s.pndService.Get(store.Query{ID: pndID})
	if err != nil {
		return nil, handleRPCError(labels, err)
	}
	err = s.pndService.Delete(pnd)
	if err != nil {
		return &ppb.DeletePndResponse{
			Timestamp: time.Now().UnixNano(),
			Status:    ppb.Status_STATUS_ERROR,
		}, err
	}

	return &ppb.DeletePndResponse{
		Timestamp: time.Now().UnixNano(),
		Status:    ppb.Status_STATUS_OK,
	}, nil
}
