"""Test cases for procedures defined in commands.py"""
import unittest

from dataclasses import dataclass
from datetime import datetime
from typing import Callable
from unittest import mock
from gn3.commands import compose_gemma_cmd
from gn3.commands import compose_rqtl_cmd
from gn3.commands import queue_cmd
from gn3.commands import run_cmd
from gn3.exceptions import RedisConnectionError


@dataclass
class MockRedis:
    """Mock Redis connection"""
    ping: Callable
    hset: mock.MagicMock
    rpush: mock.MagicMock


class TestCommands(unittest.TestCase):
    """Test cases for commands.py"""

    def test_compose_gemma_cmd_no_extra_args(self):
        """Test that the gemma cmd is composed correctly"""
        self.assertEqual(
            compose_gemma_cmd(gemma_wrapper_cmd="gemma-wrapper",
                              gemma_kwargs={
                                  "g": "/tmp/genofile.txt",
                                  "p": "/tmp/gf13Ad0tRX/phenofile.txt"
                              },
                              gemma_args=["-gk"]),
            ("gemma-wrapper --json -- "
             "-g /tmp/genofile.txt "
             "-p /tmp/gf13Ad0tRX/phenofile.txt"
             " -gk"))

    def test_compose_gemma_cmd_extra_args(self):
        """Test that the gemma cmd is composed correctly"""
        self.assertEqual(
            compose_gemma_cmd(gemma_wrapper_cmd="gemma-wrapper",
                              gemma_wrapper_kwargs={
                                  "loco": "1,2,3,4"
                              },
                              gemma_kwargs={
                                  "g": "/tmp/genofile.txt",
                                  "p": "/tmp/gf13Ad0tRX/phenofile.txt"
                              },
                              gemma_args=["-gk"]),
            ("gemma-wrapper --json --loco 1,2,3,4 -- "
             "-g /tmp/genofile.txt "
             "-p /tmp/gf13Ad0tRX/phenofile.txt"
             " -gk"))

    def test_compose_rqtl_cmd(self):
        """Test that the R/qtl cmd is composed correctly"""
        self.assertEqual(
            compose_rqtl_cmd(rqtl_wrapper_cmd="rqtl-wrapper",
                             rqtl_wrapper_kwargs={
                                 "g": "the_genofile",
                                 "p": "the_phenofile",
                                 "model": "np",
                                 "method": "ehk",
                                 "nperm": 2000,
                                 "scale": "Mb",
                                 "control": "rs234567"
                             },
                             rqtl_wrapper_bool_kwargs=[
                                 "addcovar"
                             ]),
            ("Rscript rqtl-wrapper "
             "--g the_genofile --p the_phenofile "
             "--model np --method ehk "
             "--nperm 2000 --scale Mb "
             "--control rs234567 "
             "--addcovar")
        )

    def test_queue_cmd_exception_raised_when_redis_is_down(self):
        """Test that the correct error is raised when Redis is unavailable"""
        self.assertRaises(RedisConnectionError,
                          queue_cmd,
                          cmd="ls",
                          job_queue="GN2::job-queue",
                          conn=MockRedis(ping=lambda: False,
                                         hset=mock.MagicMock(),
                                         rpush=mock.MagicMock()))

    @mock.patch("gn3.commands.datetime")
    @mock.patch("gn3.commands.uuid4")
    def test_queue_cmd_correct_calls_to_redis(self, mock_uuid4,
                                              mock_datetime):
        """Test that the cmd is queued properly"""
        mock_uuid4.return_value = 1234
        mock_datetime.now.return_value = datetime.fromisoformat('2021-02-12 '
                                                                '17:32:24.'
                                                                '859097')
        mock_redis_conn = MockRedis(ping=lambda: True,
                                    hset=mock.MagicMock(),
                                    rpush=mock.MagicMock())
        actual_unique_id = "cmd::2021-02-1217-3224-3224-1234"
        self.assertEqual(queue_cmd(cmd="ls",
                                   conn=mock_redis_conn,
                                   job_queue="GN2::job-queue"),
                         actual_unique_id)
        mock_redis_conn.hset.assert_has_calls(
            [mock.call(name=actual_unique_id, key="cmd", value="ls"),
             mock.call(name=actual_unique_id, key="result", value=""),
             mock.call(name=actual_unique_id, key="status", value="queued")])
        mock_redis_conn.rpush.assert_has_calls(
            [mock.call("GN2::job-queue", actual_unique_id)])

    @mock.patch("gn3.commands.datetime")
    @mock.patch("gn3.commands.uuid4")
    def test_queue_cmd_right_calls_to_redis_with_email(self,
                                                       mock_uuid4,
                                                       mock_datetime):
        """Test that the cmd is queued properly when given the email"""
        mock_uuid4.return_value = 1234
        mock_datetime.now.return_value = datetime.fromisoformat('2021-02-12 '
                                                                '17:32:24.'
                                                                '859097')
        mock_redis_conn = MockRedis(ping=lambda: True,
                                    hset=mock.MagicMock(),
                                    rpush=mock.MagicMock())
        actual_unique_id = "cmd::2021-02-1217-3224-3224-1234"
        self.assertEqual(queue_cmd(cmd="ls",
                                   conn=mock_redis_conn,
                                   job_queue="GN2::job-queue",
                                   email="me@me.com"),
                         actual_unique_id)
        mock_redis_conn.hset.assert_has_calls(
            [mock.call(name=actual_unique_id, key="cmd", value="ls"),
             mock.call(name=actual_unique_id, key="result", value=""),
             mock.call(name=actual_unique_id, key="status", value="queued"),
             mock.call(name=actual_unique_id, key="email", value="me@me.com")
             ])
        mock_redis_conn.rpush.assert_has_calls(
            [mock.call("GN2::job-queue", actual_unique_id)])

    def test_run_cmd_correct_input(self):
        """Test that a correct cmd is processed correctly"""
        self.assertEqual(run_cmd("echo test"),
                         {"code": 0, "output": "test\n"})

    def test_run_cmd_incorrect_input(self):
        """Test that an incorrect cmd is processed correctly"""
        result = run_cmd("echoo test")
        self.assertEqual(127, result.get("code"))
        self.assertIn("not found", result.get("output"))