# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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.

import logging, pytest
from unittest.mock import patch
from opticalcentralizedattackdetector.Config import GRPC_SERVICE_PORT, GRPC_MAX_WORKERS, GRPC_GRACE_PERIOD
from opticalcentralizedattackdetector.client.OpticalCentralizedAttackDetectorClient import OpticalCentralizedAttackDetectorClient
from opticalcentralizedattackdetector.proto.context_pb2 import ContextIdList, ContextId, Empty, Service, ContextId, ServiceList
from opticalcentralizedattackdetector.proto.monitoring_pb2 import Kpi, KpiList
from opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorService import OpticalCentralizedAttackDetectorService
from .example_objects import CONTEXT_ID, CONTEXT_ID_2, SERVICE_DEV1_DEV2

port = 10000 + GRPC_SERVICE_PORT # avoid privileged ports

LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)

@pytest.fixture(scope='session')
def optical_centralized_attack_detector_service():
    _service = OpticalCentralizedAttackDetectorService(
        port=port, max_workers=GRPC_MAX_WORKERS, grace_period=GRPC_GRACE_PERIOD)
    # mocker_context_client = mock.patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client')
    # mocker_context_client.start()

    # mocker_influx_db = mock.patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client')
    # mocker_influx_db.start()

    _service.start()
    yield _service
    _service.stop()
    # mocker_context_client.stop()
    # mocker_influx_db.stop()

@pytest.fixture(scope='session')
def optical_centralized_attack_detector_client(optical_centralized_attack_detector_service):
    _client = OpticalCentralizedAttackDetectorClient(address='127.0.0.1', port=port)
    yield _client
    _client.close()

def test_notify_service_update(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient):
    service = Service()
    optical_centralized_attack_detector_client.NotifyServiceUpdate(service)

def test_detect_attack_no_contexts(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb:
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)
        context.ListContextIds.assert_called_once()
        influxdb.query.assert_called_once()
        context.ListServices.assert_not_called()

def test_detect_attack_with_context(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient,):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb:
        # setting up the mock
        cid_list = ContextIdList()
        cid_list.context_ids.append(ContextId(**CONTEXT_ID))
        context.ListContextIds.return_value = cid_list

        # making the test
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)

        # checking behavior
        context.ListContextIds.assert_called_once()
        context.ListServices.assert_called_with(cid_list.context_ids[0])
        influxdb.query.assert_called_once()

def test_detect_attack_with_contexts(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient,):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb:
        # setting up the mock
        cid_list = ContextIdList()
        cid_list.context_ids.append(ContextId(**CONTEXT_ID))
        cid_list.context_ids.append(ContextId(**CONTEXT_ID_2))
        context.ListContextIds.return_value = cid_list

        # making the test
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)

        # checking behavior
        context.ListContextIds.assert_called_once()
        context.ListServices.assert_any_call(cid_list.context_ids[0])
        context.ListServices.assert_any_call(cid_list.context_ids[1])
        influxdb.query.assert_called_once()

def test_detect_attack_with_service(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient,):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.dbscanserving_client') as dbscan:

        # setting up the mock
        cid_list = ContextIdList()
        cid_list.context_ids.append(ContextId(**CONTEXT_ID))
        context.ListContextIds.return_value = cid_list

        service_list = ServiceList()
        service_list.services.append(Service(**SERVICE_DEV1_DEV2))
        context.ListServices.return_value = service_list

        influxdb.query.return_value.get_points.return_value = [(1, 2), (3, 4)]

        # making the test
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)

        # checking behavior
        context.ListContextIds.assert_called_once()
        context.ListServices.assert_called_with(cid_list.context_ids[0])
        influxdb.query.assert_called_once()
        dbscan.Detect.assert_called()

def test_detect_attack_no_attack(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient,):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.dbscanserving_client') as dbscan, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.attack_mitigator_client') as mitigator:

        # setting up the mock
        cid_list = ContextIdList()
        cid_list.context_ids.append(ContextId(**CONTEXT_ID))
        context.ListContextIds.return_value = cid_list

        service_list = ServiceList()
        service_list.services.append(Service(**SERVICE_DEV1_DEV2))
        context.ListServices.return_value = service_list

        # dbscan.Detect.return_value = object()
        dbscan.Detect.return_value.cluster_indices = [0, 1, 2, 3, 4, 5]

        # making the test
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)

        # checking behavior
        context.ListContextIds.assert_called_once()
        context.ListServices.assert_called_with(cid_list.context_ids[0])
        influxdb.query.assert_called_once()
        dbscan.Detect.assert_called()
        mitigator.NotifyAttack.assert_not_called()

def test_detect_attack_with_attack(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient,):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.dbscanserving_client') as dbscan, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.attack_mitigator_client') as mitigator:

        # setting up the mock
        cid_list = ContextIdList()
        cid_list.context_ids.append(ContextId(**CONTEXT_ID))
        context.ListContextIds.return_value = cid_list

        service_list = ServiceList()
        service_list.services.append(Service(**SERVICE_DEV1_DEV2))
        context.ListServices.return_value = service_list

        # dbscan.Detect.return_value = object()
        dbscan.Detect.return_value.cluster_indices = [0, 1, 2, 3, 4, -1]

        # making the test
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)

        # checking behavior
        context.ListContextIds.assert_called_once()
        context.ListServices.assert_called_with(cid_list.context_ids[0])
        influxdb.query.assert_called_once()
        dbscan.Detect.assert_called()
        mitigator.NotifyAttack.assert_called()

def test_report_summarized_kpi(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient):
    kpi_list = KpiList()
    optical_centralized_attack_detector_client.ReportSummarizedKpi(kpi_list)

def test_report_kpi(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient):
    kpi_list = KpiList()
    optical_centralized_attack_detector_client.ReportKpi(kpi_list)
