/*
 * Copyright 2021-2024 Open Networking Foundation (ONF) and the ONF Contributors

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

 * http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Package mocks provides the mocks for openolt-adapter.
package mocks

import (
	"context"
	"errors"
	"fmt"
	"strings"

	vgrpc "github.com/opencord/voltha-lib-go/v7/pkg/grpc"
	"github.com/opencord/voltha-protos/v5/go/common"
	ca "github.com/opencord/voltha-protos/v5/go/core_adapter"
	"github.com/opencord/voltha-protos/v5/go/core_service"
	"github.com/opencord/voltha-protos/v5/go/voltha"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/types/known/emptypb"
)

// NewMockCoreClient creates a new mock core client for a given core service
func NewMockCoreClient(coreService *MockCoreService) *vgrpc.Client {
	cc, _ := vgrpc.NewClient("mock-core-endpoint", "mock-server-endpoint", "core_service.CoreService", nil)
	cc.SetService(coreService)
	return cc
}

// MockCoreService represents a mock core service
type MockCoreService struct {
	// Values to be used in test can reside inside this structure
	// TODO store relevant info in this, use this info for negative and positive tests
	Devices     map[string]*voltha.Device
	DevicePorts map[string][]*voltha.Port
}

// RegisterAdapter implements mock RegisterAdapter
func (mcs MockCoreService) RegisterAdapter(ctx context.Context, in *ca.AdapterRegistration, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if ctx == nil || in.Adapter == nil || in.DTypes == nil {
		return nil, errors.New("registerAdapter func parameters cannot be nil")
	}
	return &emptypb.Empty{}, nil
}

// DeviceUpdate implements mock DeviceUpdate
func (mcs MockCoreService) DeviceUpdate(ctx context.Context, in *voltha.Device, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if in.Id == "" {
		return nil, errors.New("no Device")
	}
	return &emptypb.Empty{}, nil
}

// PortCreated implements mock PortCreated
func (mcs MockCoreService) PortCreated(ctx context.Context, in *voltha.Port, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if in.DeviceId == "" {
		return nil, errors.New("no deviceID")
	}
	if in.Type > 7 {
		return nil, errors.New("invalid porttype")
	}
	return &emptypb.Empty{}, nil
}

// PortsStateUpdate implements mock PortsStateUpdate
func (mcs MockCoreService) PortsStateUpdate(ctx context.Context, in *ca.PortStateFilter, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if in.DeviceId == "" {
		return nil, errors.New("no Device")
	}
	return &emptypb.Empty{}, nil
}

// DeleteAllPorts implements mock DeleteAllPorts
func (mcs MockCoreService) DeleteAllPorts(ctx context.Context, in *common.ID, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if in.Id == "" {
		return nil, errors.New("no Device id")
	}
	return &emptypb.Empty{}, nil
}

// GetDevicePort implements mock GetDevicePort
func (mcs MockCoreService) GetDevicePort(ctx context.Context, in *ca.PortFilter, opts ...grpc.CallOption) (*voltha.Port, error) {
	for _, port := range mcs.DevicePorts[in.DeviceId] {
		if port.PortNo == in.Port {
			return port, nil
		}
	}
	return nil, errors.New("device/port not found")
}

// ListDevicePorts implements mock ListDevicePorts
func (mcs MockCoreService) ListDevicePorts(ctx context.Context, in *common.ID, opts ...grpc.CallOption) (*voltha.Ports, error) {
	ports, have := mcs.DevicePorts[in.Id]
	if !have {
		return nil, errors.New("device id not found")
	}
	return &voltha.Ports{Items: ports}, nil
}

// DeviceStateUpdate implements mock DeviceStateUpdate
func (mcs MockCoreService) DeviceStateUpdate(ctx context.Context, in *ca.DeviceStateFilter, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if in.DeviceId == "" {
		return nil, errors.New("no Device id")
	}
	return &emptypb.Empty{}, nil
}

// DevicePMConfigUpdate implements mock DevicePMConfigUpdate
func (mcs MockCoreService) DevicePMConfigUpdate(ctx context.Context, in *voltha.PmConfigs, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, nil
}

// ChildDeviceDetected implements mock ChildDeviceDetected
func (mcs MockCoreService) ChildDeviceDetected(ctx context.Context, in *ca.DeviceDiscovery, opts ...grpc.CallOption) (*voltha.Device, error) {
	if in.ParentId == "" {
		return nil, errors.New("no deviceID")
	}
	for k, v := range mcs.Devices {
		if strings.Contains(k, "onu") {
			return v, nil
		}
	}
	return nil, errors.New("no deviceID")
}

// ChildDevicesLost implements mock ChildDevicesLost
func (mcs MockCoreService) ChildDevicesLost(ctx context.Context, in *common.ID, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if in.Id == "" {
		return nil, errors.New("no device id")
	}
	return &emptypb.Empty{}, nil
}

// ChildDevicesDetected implements mock ChildDevicesDetected
func (mcs MockCoreService) ChildDevicesDetected(ctx context.Context, in *common.ID, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if in.Id == "" {
		return nil, errors.New("no device id")
	}
	return &emptypb.Empty{}, nil
}

// GetDevice implements mock GetDevice
func (mcs MockCoreService) GetDevice(ctx context.Context, in *common.ID, opts ...grpc.CallOption) (*voltha.Device, error) {
	if in.Id == "" {
		return &voltha.Device{}, errors.New("no deviceID")
	}
	for k, v := range mcs.Devices {
		if k == "olt" {
			return v, nil
		}
	}
	return nil, errors.New("device detection failed")
}

// GetChildDevice implements mock GetChildDevice
func (mcs MockCoreService) GetChildDevice(
	ctx context.Context,
	in *ca.ChildDeviceFilter,
	opts ...grpc.CallOption,
) (*voltha.Device, error) {
	if in.ParentId == "" {
		return nil, errors.New("device detection failed")
	}
	if in.OnuId == nil {
		return nil, errors.New("device detection failed")
	}
	var onuDevice *voltha.Device
	for _, val := range mcs.Devices {
		if val.GetId() == fmt.Sprintf("%d", *in.OnuId) {
			onuDevice = val
			break
		}
	}
	if onuDevice != nil {
		return onuDevice, nil
	}
	return nil, errors.New("device detection failed")
}

// GetChildDevices implements mock GetChildDevices
func (mcs MockCoreService) GetChildDevices(ctx context.Context, in *common.ID, opts ...grpc.CallOption) (*voltha.Devices, error) {
	if in.Id == "" {
		return nil, errors.New("no deviceID")
	}
	onuDevices := make([]*voltha.Device, 0)

	for _, val := range mcs.Devices {
		if val != nil && val.ParentId == in.Id {
			onuDevices = append(onuDevices, val)
		}
	}

	deviceList := &voltha.Devices{Items: onuDevices}
	if len(deviceList.Items) > 0 {
		return deviceList, nil
	}
	return nil, errors.New("device detection failed")
}

// SendPacketIn implements mock SendPacketIn
func (mcs MockCoreService) SendPacketIn(ctx context.Context, in *ca.PacketIn, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if in.DeviceId == "" {
		return nil, errors.New("no Device ID")
	}
	return &emptypb.Empty{}, nil
}

// DeviceReasonUpdate implements mock DeviceReasonUpdate
func (mcs MockCoreService) DeviceReasonUpdate(ctx context.Context, in *ca.DeviceReason, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if in.DeviceId == "" {
		return nil, errors.New("no Device ID")
	}
	return &emptypb.Empty{}, nil
}

// PortStateUpdate implements mock PortStateUpdate
func (mcs MockCoreService) PortStateUpdate(ctx context.Context, in *ca.PortState, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	if in.DeviceId == "" {
		return nil, errors.New("no Device")
	}
	return &emptypb.Empty{}, nil
}

// GetHealthStatus implements mock GetHealthStatus
func (mcs MockCoreService) GetHealthStatus(ctx context.Context, opts ...grpc.CallOption) (core_service.CoreService_GetHealthStatusClient, error) {
	return nil, nil
}

// Additional API found in the Core - unused?

// ReconcileChildDevices implements mock ReconcileChildDevices
func (mcs MockCoreService) ReconcileChildDevices(ctx context.Context, in *common.ID, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, nil
}

// GetChildDeviceWithProxyAddress implements mock GetChildDeviceWithProxyAddress
func (mcs MockCoreService) GetChildDeviceWithProxyAddress(ctx context.Context, in *voltha.Device_ProxyAddress, opts ...grpc.CallOption) (*voltha.Device, error) {
	return nil, nil
}

// GetPorts implements mock GetPorts
func (mcs MockCoreService) GetPorts(ctx context.Context, in *ca.PortFilter, opts ...grpc.CallOption) (*voltha.Ports, error) {
	return nil, nil
}

// ChildrenStateUpdate implements mock ChildrenStateUpdate
func (mcs MockCoreService) ChildrenStateUpdate(ctx context.Context, in *ca.DeviceStateFilter, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, nil
}

// UpdateImageDownload implements mock UpdateImageDownload
func (mcs MockCoreService) UpdateImageDownload(ctx context.Context, in *voltha.ImageDownload, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, nil
}
