# -*- coding: utf-8 eval: (blacken-mode 1) -*-
# SPDX-License-Identifier: GPL-2.0-or-later
#
# February 21 2022, Christian Hopps <chopps@labn.net>
#
# Copyright (c) 2022, LabN Consulting, L.L.C.
#
"""
test_basic_grpc.py: Test Basic gRPC.
"""

import json
import logging
import os
import re
import sys

import pytest
from lib.common_config import step
from lib.micronet import commander
from lib.topogen import Topogen, TopoRouter
from lib.topotest import json_cmp

CWD = os.path.dirname(os.path.realpath(__file__))

GRPCP_ZEBRA = 50051
GRPCP_STATICD = 50052
GRPCP_BFDD = 50053
GRPCP_ISISD = 50054
GRPCP_OSPFD = 50055
GRPCP_PIMD = 50056
GRPCP_MGMTD = 50057

pytestmark = [
    pytest.mark.mgmtd,
    # pytest.mark.bfdd,
    # pytest.mark.isisd,
    # pytest.mark.ospfd,
    # pytest.mark.pimd,
    pytest.mark.staticd,
]

script_path = os.path.realpath(os.path.join(CWD, "../lib/grpc-query.py"))

try:
    commander.cmd_raises([script_path, "--check"])
except Exception:
    pytest.skip(
        "skipping; cannot create or import gRPC proto modules", allow_module_level=True
    )


@pytest.fixture(scope="module")
def tgen(request):
    "Setup/Teardown the environment and provide tgen argument to tests"
    topodef = {"s1": ("r1", "r2")}
    tgen = Topogen(topodef, request.module.__name__)

    tgen.start_topology()
    router_list = tgen.routers()

    for _, router in router_list.items():
        router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf", f"-M grpc:{GRPCP_ZEBRA}")
        router.load_config(TopoRouter.RD_STATIC, "", f"-M grpc:{GRPCP_STATICD}")
        # router.load_config(TopoRouter.RD_BFDD, "", f"-M grpc:{GRPCP_BFDD}")
        # router.load_config(TopoRouter.RD_ISIS, None, f"-M grpc:{GRPCP_ISISD}")
        # router.load_config(TopoRouter.RD_OSPF, None, f"-M grpc:{GRPCP_OSPFD}")
        # router.load_config(TopoRouter.RD_PIM, None, f"-M grpc:{GRPCP_PIMD}")

        # This doesn't work yet...
        # router.load_config(TopoRouter.RD_MGMTD, "", f"-M grpc:{GRPCP_MGMTD}")

    tgen.start_router()
    yield tgen

    logging.info("Stopping all routers (no assert on error)")
    tgen.stop_topology()


# Let's not do this so we catch errors
# Fixture that executes before each test
@pytest.fixture(autouse=True)
def skip_on_failure(tgen):
    if tgen.routers_have_failure():
        pytest.skip("skipped because of previous test failure")


# ===================
# The tests functions
# ===================


def run_grpc_client(r, port, commands):
    if not isinstance(commands, str):
        commands = "\n".join(commands) + "\n"
    if not commands.endswith("\n"):
        commands += "\n"
    return r.cmd_raises([script_path, f"--port={port}"], stdin=commands)


def test_connectivity(tgen):
    tgen.gears["r1"].cmd_raises("ping -c1 192.168.1.2")


def test_capabilities(tgen):
    r1 = tgen.gears["r1"]
    output = run_grpc_client(r1, GRPCP_STATICD, "GETCAP")
    logging.debug("grpc output: %s", output)

    modules = sorted(re.findall('name: "([^"]+)"', output))
    expected = ["frr-interface", "frr-routing", "frr-staticd", "frr-vrf"]
    assert modules == expected

    encodings = sorted(re.findall("supported_encodings: (.*)", output))
    expected = ["JSON", "XML"]
    assert encodings == expected


def test_get_config(tgen):
    nrepeat = 5
    r1 = tgen.gears["r1"]

    step("'GET' interface config and state 10 times, once per invocation")

    for i in range(0, nrepeat):
        output = run_grpc_client(r1, GRPCP_ZEBRA, "GET-CONFIG,/frr-interface:lib")
        logging.debug("[iteration %s]: grpc GET output: %s", i, output)

    step(f"'GET' YANG {nrepeat} times in one invocation")
    commands = ["GET-CONFIG,/frr-interface:lib" for _ in range(0, 10)]
    output = run_grpc_client(r1, GRPCP_ZEBRA, commands)
    logging.debug("grpc GET*{%d} output: %s", nrepeat, output)

    output = run_grpc_client(r1, GRPCP_ZEBRA, commands[0])
    out_json = json.loads(output)
    expect = json.loads(
        """{
  "frr-interface:lib": {
    "interface": [
      {
        "name": "r1-eth0",
        "frr-zebra:zebra": {
          "ipv4-addrs": [
            {
              "ip": "192.168.1.1",
              "prefix-length": 24
            }
          ],
          "evpn-mh": {},
          "ipv6-router-advertisements": {}
        }
      }
    ]
  },
  "frr-zebra:zebra": {
    "import-kernel-table": {}
  }
} """
    )
    result = json_cmp(out_json, expect, exact=True)
    assert result is None


def test_get_vrf_config(tgen):
    r1 = tgen.gears["r1"]

    step("'GET' VRF config and state")

    output = run_grpc_client(r1, GRPCP_STATICD, "GET,/frr-vrf:lib")
    logging.debug("grpc GET /frr-vrf:lib output: %s", output)
    out_json = json.loads(output)
    expect = json.loads(
        """{
  "frr-vrf:lib": {
    "vrf": [
      {
        "name": "default",
        "state": {
          "id": 0,
          "active": true
        }
      }
    ]
  }
}
    """
    )
    result = json_cmp(out_json, expect, exact=True)
    assert result is None


def test_shutdown_checks(tgen):
    # Start a process rnuning that will fetch bunches of data then shut the routers down
    # and check for cores.
    nrepeat = 100
    r1 = tgen.gears["r1"]
    commands = ["GET,/frr-interface:lib" for _ in range(0, nrepeat)]
    p = r1.popen([script_path, f"--port={GRPCP_ZEBRA}"] + commands)
    import time

    time.sleep(1)
    try:
        for r in tgen.routers().values():
            r.net.stopRouter(False)
            r.net.checkRouterCores()
    finally:
        if p:
            p.terminate()
            p.wait()


# Memory leak test template
# Not compatible with the shutdown check above
def _test_memory_leak(tgen):
    "Run the memory leak test and report results."

    if not tgen.is_memleak_enabled():
        pytest.skip("Memory leak test/report is disabled")

    tgen.report_memory_leaks()


if __name__ == "__main__":
    args = ["-s"] + sys.argv[1:]
    sys.exit(pytest.main(args))
