# Copyright 2021 Northern.tech AS
#
#    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.

from typing import Iterable
import uuid
import pytest
import requests
import difflib, pprint

from common import management_api_with_params
from common import InternalAPIClient


@pytest.fixture
def device_id():
    client = InternalAPIClient()
    device_id = str(uuid.uuid4())
    new_device = {"device_id": device_id}
    r = client.provision_device_with_http_info(
        tenant_id="tenant-id", new_device=new_device
    )
    assert r.status_code == 201
    yield device_id
    r = client.decommission_device_with_http_info(
        tenant_id="tenant-id", device_id=device_id
    )
    assert r.status_code == 204


def dict_diff(subset: dict, superset: dict) -> dict:
    err = {}
    for key, value in subset.items():
        if key not in superset:
            err[key] = value
        if isinstance(value, dict) and isinstance(superset[key], dict):
            sub_err = dict_diff(value, superset[key])
            if sub_err:
                err[key] = sub_err
        elif isinstance(value, Iterable) and isinstance(superset[key], type(value)):
            # Check that values in subset is contained in superset and
            # collect missing items.
            a = set(value)
            b = set(superset[key])
            errs = a.difference(b).intersection(a)
            if errs:
                err[key] = {"expected": value, "actual": errs}
        elif superset[key] != value:
            err[key] = {"expected": value, "actual": superset[key]}
    return err


class TestAuditlogs:
    def test_config_device_set_auditlog(self, device_id, mmock_url):
        user_id = str(uuid.uuid4())
        client = management_api_with_params(user_id=user_id, tenant_id="tenant-id")
        #
        # set the initial configuration
        configuration = {
            "key": "value",
            "another-key": "another-value",
            "dollar-key": "$",
        }
        r = client.set_device_configuration_with_http_info(
            device_id, request_body=configuration
        )
        assert r.status_code == 204
        #
        # verify the auditlogs
        res = requests.get(mmock_url + "/api/request/all")
        assert res.status_code == 200
        response = res.json()
        assert len(response) == 1
        expected = {
            "request": {
                "scheme": "http",
                "host": "mender-workflows-server",
                "port": "8080",
                "method": "POST",
                "path": "/api/v1/workflow/emit_auditlog",
                "queryStringParameters": {},
                "fragment": "",
                "headers": {
                    "Content-Type": ["application/json"],
                    "Accept-Encoding": ["gzip"],
                    "User-Agent": ["OpenAPI-Generator/1.0.0/go"],
                    "X-Men-Requestid": [],
                },
                "cookies": {},
            },
        }
        body = response[0]["request"].pop("body")
        req_diff = dict_diff(expected["request"], response[0]["request"])
        assert not req_diff, (
            "did not receive expected request: diff follows:\n"
            + pprint.pformat(req_diff)
        )
        assert "set_configuration" in body

    def test_config_device_deploy_auditlog(self, device_id, mmock_url):
        user_id = str(uuid.uuid4())
        client = management_api_with_params(user_id=user_id, tenant_id="tenant-id")
        #
        # set the initial configuration
        configuration = {
            "key": "value",
            "another-key": "another-value",
            "dollar-key": "$",
        }
        r = client.set_device_configuration_with_http_info(
            device_id, request_body=configuration
        )
        assert r.status_code == 204
        #
        # deploy the configuration
        request = {
            "retries": 1,
        }
        r = client.deploy_device_configuration_with_http_info(
            device_id, new_configuration_deployment=request
        )
        assert r.status_code == 200
        assert r.data is not None and r.data.deployment_id is not None
        #
        # verify the auditlogs
        res = requests.get(mmock_url + "/api/request/all")
        assert res.status_code == 200
        responses = res.json()
        assert len(responses) == 3
        expected = [
            {
                "request": {
                    "scheme": "http",
                    "host": "mender-workflows-server",
                    "port": "8080",
                    "method": "POST",
                    "path": "/api/v1/workflow/emit_auditlog",
                    "queryStringParameters": {},
                    "fragment": "",
                    "headers": {
                        "Accept": ["application/json"],
                        "Content-Type": ["application/json"],
                        "Accept-Encoding": ["gzip"],
                        "User-Agent": ["OpenAPI-Generator/1.0.0/go"],
                        "X-Men-Requestid": [],
                    },
                    "cookies": {},
                },
                "contains": "set_configuration",
            },
            {
                "request": {
                    "scheme": "http",
                    "host": "mender-workflows-server",
                    "port": "8080",
                    "method": "POST",
                    "path": "/api/v1/workflow/deploy_device_configuration",
                    "queryStringParameters": {},
                    "fragment": "",
                    "headers": {
                        "Accept": ["application/json"],
                        "Content-Type": ["application/json"],
                        "Accept-Encoding": ["gzip"],
                        "User-Agent": ["OpenAPI-Generator/1.0.0/go"],
                        "X-Men-Requestid": [],
                    },
                    "cookies": {},
                },
            },
            {
                "request": {
                    "scheme": "http",
                    "host": "mender-workflows-server",
                    "port": "8080",
                    "method": "POST",
                    "path": "/api/v1/workflow/emit_auditlog",
                    "queryStringParameters": {},
                    "fragment": "",
                    "headers": {
                        "Accept": ["application/json"],
                        "Content-Type": ["application/json"],
                        "Accept-Encoding": ["gzip"],
                        "User-Agent": ["OpenAPI-Generator/1.0.0/go"],
                        "X-Men-Requestid": [],
                    },
                    "cookies": {},
                },
                "contains": "deploy_configuration",
            },
        ]
        for expected, response in zip(expected, responses):
            body = response["request"].pop("body")
            req_diff = dict_diff(expected["request"], response["request"])
            assert not req_diff, (
                "did not receive expected request: diff follows:\n"
                + pprint.pformat(req_diff)
            )
            if expected.get("contains"):
                assert expected["contains"] in body
