from http.server import BaseHTTPRequestHandler, HTTPServer
import socket
from threading import Thread
import requests
import pymongo

from scout.server.blueprints.cases import controllers
from scout.parse.matchmaker import genomic_features, omim_terms, hpo_terms

MME_ACCEPTS = "application/vnd.ga4gh.matchmaker.v1.0+json"
MME_TOKEN = "test_token"

# mock matchmaker server to send responses when it get requests
class MockServerRequestHandler(BaseHTTPRequestHandler):
    def _set_headers(self):
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.end_headers()

    def _send_ok(self):
        self._set_headers()
        # return the simplest ok response:
        self.wfile.write(bytes('{"status_code": 200}', "utf-8"))

    def do_POST(self):
        self._send_ok()

    def do_DELETE(self):
        self._send_ok()

    def do_GET(self, match_objs):  # mocks get MME matches
        self._send_ok()


def get_free_port():
    s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
    s.bind(("localhost", 0))
    address, port = s.getsockname()
    s.close()
    return port


class TestMockMatchMakerServer(object):
    @classmethod
    def setup_class(cls):
        # Configure mock server.
        cls.mock_server_port = get_free_port()
        cls.mock_server = HTTPServer(
            ("localhost", cls.mock_server_port), MockServerRequestHandler
        )

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

    def test_mme_add(self, populated_database, user_obj, case_obj, parsed_variant):
        """test calling the controller that submits new patients to Matchmaker"""

        session = requests.Session()
        adapter = populated_database

        # Add an HPO term to this scout case
        assert case_obj.get("phenotype_terms") == None
        phenotype_term = {
            "phenotype_id": "HP:0011031",
            "feature": "Abnormality of iron homeostasis",
        }
        updated_case = adapter.case_collection.find_one_and_update(
            {"_id": case_obj["_id"]},
            {"$set": {"phenotype_terms": [phenotype_term]}},
            return_document=pymongo.ReturnDocument.AFTER,
        )
        assert updated_case["phenotype_terms"][0] == phenotype_term

        # Check that features (HPO) terms are obtained by MME parser
        features = hpo_terms(updated_case)
        assert features

        # Add a couple of OMIM diagnoses to this case
        assert case_obj.get("diagnosis_phenotypes") == None
        updated_case = adapter.case_collection.find_one_and_update(
            {"_id": case_obj["_id"]},
            {"$set": {"diagnosis_phenotypes": [615349, 616833]}},
            return_document=pymongo.ReturnDocument.AFTER,
        )
        assert updated_case["diagnosis_phenotypes"]

        # Check that OMIM terms are obtained by MME parser
        diagnoses = omim_terms(updated_case)
        assert diagnoses

        # Add variant containing a gene to database and pin it for this case
        a_gene = adapter.hgnc_collection.find_one()
        assert a_gene["hgnc_id"]
        assert adapter.variant_collection.find_one() is None
        parsed_variant["hgnc_ids"] = [a_gene["hgnc_id"]]
        parsed_variant["samples"] = [
            {  # pretend the affected subject has the allele
                "sample_id": "ADM1059A2",
                "display_name": "NA12882",
                "genotype_call": "0/1",
            }
        ]
        adapter.variant_collection.insert_one(parsed_variant)
        assert adapter.variant_collection.find_one()

        # pin it
        updated_case = adapter.case_collection.find_one_and_update(
            {"_id": case_obj["_id"]},
            {"$set": {"suspects": [parsed_variant["_id"]]}},
            return_document=pymongo.ReturnDocument.AFTER,
        )
        assert updated_case["suspects"] == [parsed_variant["_id"]]

        # Check that genomic features are created correcly by MME parser
        g_feat = genomic_features(adapter, updated_case, "NA12882", "False")
        assert g_feat

        # use controller to send request and collect response
        mme_base_url = "http://localhost:{port}".format(port=self.mock_server_port)

        submitted_info = controllers.mme_add(
            store=adapter,
            user_obj=user_obj,
            case_obj=updated_case,
            add_gender=True,
            add_features=True,
            add_disorders=True,
            genes_only=False,
            mme_base_url=mme_base_url,
            mme_accepts=MME_ACCEPTS,
            mme_token=MME_TOKEN,
        )

        assert submitted_info["server_responses"][0]["patient"]

    def test_mme_delete(self, mme_submission, case_obj):
        """test calling the controller that deletes a patient from MatchMaker"""

        mme_base_url = "http://localhost:{port}".format(port=self.mock_server_port)
        case_obj["mme_submission"] = mme_submission
        server_responses = controllers.mme_delete(case_obj, mme_base_url, MME_TOKEN)

        assert server_responses[0]["patient_id"] == mme_submission["patients"][0]["id"]

    def test_mme_matches(self, case_obj, mme_submission, institute_obj):
        """Testing calling the controller that collects all matches for a patient"""

        mme_base_url = "http://localhost:{port}".format(port=self.mock_server_port)
        case_obj["mme_submission"] = mme_submission
        data = controllers.mme_matches(case_obj, institute_obj, mme_base_url, MME_TOKEN)
        assert data
        assert data["institute"] == institute_obj
        assert data["case"] == case_obj
        query_patient = mme_submission["patients"][0]["id"]
        assert query_patient in data["matches"]

    def test_mme_match(self, case_obj, mme_submission):
        """Testing calling the controller that triggers internal and external MME matches"""

        mme_base_url = "http://localhost:{port}".format(port=self.mock_server_port)
        case_obj["mme_submission"] = mme_submission

        ##  Test function to trigger internal matches:
        server_responses = controllers.mme_match(
            case_obj=case_obj,
            match_type="internal",
            mme_base_url=mme_base_url,
            mme_token=MME_TOKEN,
        )
        # server response will be a list of responses
        assert isinstance(server_responses, list)
        # with one element:
        assert len(server_responses) == 1
        # server response coresponds to the right patient
        assert (
            server_responses[0]["patient_id"]
            == case_obj["mme_submission"]["patients"][0]["id"]
        )
        # and the node is the internal node
        assert server_responses[0]["server"] == "Local MatchMaker node"

        ## Test Matches against external nodes:
        # Test function to trigger external matches against one node
        nodes = [{"id": "mock_node_1"}]
        server_responses = controllers.mme_match(
            case_obj=case_obj,
            match_type=nodes[0]["id"],
            mme_base_url=mme_base_url,
            mme_token=MME_TOKEN,
            nodes=nodes,
            mme_accepts=MME_ACCEPTS,
        )
        # make sure that a list of server response with one element is returned
        assert isinstance(server_responses, list)
        assert len(server_responses) == 1
        # server response coresponds to the right patient
        assert (
            server_responses[0]["patient_id"]
            == case_obj["mme_submission"]["patients"][0]["id"]
        )
        # and the node is the external node
        assert server_responses[0]["server"] == "mock_node_1"

        # Test function to trigger external matches against all nodes:
        # add another node:
        nodes.append({"id": "mock_node_2"})
        server_responses = controllers.mme_match(
            case_obj=case_obj,
            match_type="external",
            mme_base_url=mme_base_url,
            mme_token=MME_TOKEN,
            nodes=nodes,
            mme_accepts=MME_ACCEPTS,
        )
        # server response will be a list of responses
        assert isinstance(server_responses, list)
        # there will be one response for each node (2)
        assert len(server_responses) == 2

        node_ids = [node["id"] for node in nodes]
        for i, resp in enumerate(server_responses):
            # matching of patient 1 on each node returns a matching for the right patient
            assert (
                server_responses[i]["patient_id"]
                == case_obj["mme_submission"]["patients"][0]["id"]
            )
            # both nodes were interrogated
            assert server_responses[i]["server"] in node_ids
