#!/usr/bin/env python3

# sidecar_test.py
#
# This source file is part of the FoundationDB open source project
#
# Copyright 2018-2022 Apple Inc. and the FoundationDB project authors
#
# 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 os
import shutil
import socket
import tempfile
import unittest
from functools import partial
from http.server import HTTPServer
from threading import Thread
from unittest.mock import MagicMock

import requests

from sidecar import SidecarHandler


# This test suite starts a real server with a mocked configuration and will do some requests against it.
class TestSidecar(unittest.TestCase):
    def setUp(self):
        super(TestSidecar, self).setUp()
        self.get_free_port()
        self.server_url = f"http://localhost:{self.test_server_port}"
        self.mock_config = MagicMock()
        # We don't want to use TLS for the local tests for now.
        self.mock_config.enable_tls = False
        self.mock_config.output_dir = tempfile.mkdtemp()

        handler = partial(
            SidecarHandler,
            self.mock_config,
        )
        self.mock_server = HTTPServer(("localhost", self.test_server_port), handler)

        # Start running mock server in a separate thread.
        # Daemon threads automatically shut down when the main process exits.
        self.mock_server_thread = Thread(target=self.mock_server.serve_forever)
        self.mock_server_thread.setDaemon(True)
        self.mock_server_thread.start()

    def tearDown(self):
        shutil.rmtree(self.mock_config.output_dir)
        super(TestSidecar, self).tearDown()

    # Helper method to get a free port
    def get_free_port(self):
        s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
        s.bind(("localhost", 0))
        __, port = s.getsockname()
        s.close()
        self.test_server_port = port

    def test_get_ready(self):
        r = requests.get(f"{self.server_url }/ready")
        self.assertEqual(r.status_code, 200)
        self.assertEqual(r.text, "OK\n")

    def test_get_substitutions(self):
        expected = {"key": "value"}
        self.mock_config.substitutions = expected
        r = requests.get(f"{self.server_url }/substitutions")
        self.assertEqual(r.status_code, 200)
        self.assertEqual(r.json(), expected)

    def test_get_check_hash_no_found(self):
        r = requests.get(f"{self.server_url }/check_hash/foobar")
        self.assertEqual(r.status_code, 404)
        self.assertRegex(r.text, "foobar not found")

    def test_get_check_hash(self):
        with open(os.path.join(self.mock_config.output_dir, "foobar"), "w") as f:
            f.write("hello world")
        r = requests.get(f"{self.server_url }/check_hash/foobar")
        self.assertEqual(r.status_code, 200)
        self.assertEqual(
            r.text, "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
        )

    def test_get_check_hash_nested(self):
        test_path = os.path.join(self.mock_config.output_dir, "nested/foobar")
        os.makedirs(os.path.dirname(test_path), exist_ok=True)
        with open(test_path, "w") as f:
            f.write("hello world")
        r = requests.get(f"{self.server_url }/check_hash/nested/foobar")
        self.assertEqual(r.status_code, 200)
        self.assertEqual(
            r.text, "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
        )

    def test_get_is_present_no_found(self):
        r = requests.get(f"{self.server_url }/is_present/foobar")
        self.assertEqual(r.status_code, 404)
        self.assertRegex(r.text, "foobar not found")

    def test_get_is_present(self):
        with open(os.path.join(self.mock_config.output_dir, "foobar"), "w") as f:
            f.write("hello world")
        r = requests.get(f"{self.server_url }/is_present/foobar")
        self.assertEqual(r.status_code, 200)
        self.assertEqual(r.text, "OK\n")

    def test_get_is_present_nested(self):
        test_path = os.path.join(self.mock_config.output_dir, "nested/foobar")
        os.makedirs(os.path.dirname(test_path), exist_ok=True)
        with open(test_path, "w") as f:
            f.write("hello world")
        r = requests.get(f"{self.server_url }/is_present/nested/foobar")
        self.assertEqual(r.status_code, 200)
        self.assertEqual(r.text, "OK\n")

    def test_get_not_found(self):
        r = requests.get(f"{self.server_url }/foobar")
        self.assertEqual(r.status_code, 404)
        self.assertRegex(r.text, "Path not found")


# TODO(johscheuer): Add test cases for post requests.
# TODO(johscheuer): Add test cases for TLS.
if __name__ == "__main__":
    unittest.main()
