"""The tests for the facebox component."""
from unittest.mock import Mock, mock_open, patch

import pytest
import requests
import requests_mock

from homeassistant.core import callback
from homeassistant.const import (
    ATTR_ENTITY_ID, ATTR_NAME, CONF_FRIENDLY_NAME,
    CONF_IP_ADDRESS, CONF_PORT, STATE_UNKNOWN)
from homeassistant.setup import async_setup_component
import homeassistant.components.image_processing as ip
import homeassistant.components.image_processing.facebox as fb

# pylint: disable=redefined-outer-name

MOCK_IP = '192.168.0.1'
MOCK_PORT = '8080'

# Mock data returned by the facebox API.
MOCK_ERROR = "No face found"
MOCK_FACE = {'confidence': 0.5812028911604818,
             'id': 'john.jpg',
             'matched': True,
             'name': 'John Lennon',
             'rect': {'height': 75, 'left': 63, 'top': 262, 'width': 74}}

MOCK_FILE_PATH = '/images/mock.jpg'

MOCK_JSON = {"facesCount": 1,
             "success": True,
             "faces": [MOCK_FACE]}

MOCK_NAME = 'mock_name'

# Faces data after parsing.
PARSED_FACES = [{ATTR_NAME: 'John Lennon',
                 fb.ATTR_IMAGE_ID: 'john.jpg',
                 fb.ATTR_CONFIDENCE: 58.12,
                 fb.ATTR_MATCHED: True,
                 fb.ATTR_BOUNDING_BOX: {
                     'height': 75,
                     'left': 63,
                     'top': 262,
                     'width': 74}}]

MATCHED_FACES = {'John Lennon': 58.12}

VALID_ENTITY_ID = 'image_processing.facebox_demo_camera'
VALID_CONFIG = {
    ip.DOMAIN: {
        'platform': 'facebox',
        CONF_IP_ADDRESS: MOCK_IP,
        CONF_PORT: MOCK_PORT,
        ip.CONF_SOURCE: {
            ip.CONF_ENTITY_ID: 'camera.demo_camera'}
        },
    'camera': {
        'platform': 'demo'
        }
    }


@pytest.fixture
def mock_isfile():
    """Mock os.path.isfile."""
    with patch('homeassistant.components.image_processing.facebox.cv.isfile',
               return_value=True) as _mock_isfile:
        yield _mock_isfile


@pytest.fixture
def mock_open_file():
    """Mock open."""
    mopen = mock_open()
    with patch('homeassistant.components.image_processing.facebox.open',
               mopen, create=True) as _mock_open:
        yield _mock_open


def test_encode_image():
    """Test that binary data is encoded correctly."""
    assert fb.encode_image(b'test') == 'dGVzdA=='


def test_get_matched_faces():
    """Test that matched_faces are parsed correctly."""
    assert fb.get_matched_faces(PARSED_FACES) == MATCHED_FACES


def test_parse_faces():
    """Test parsing of raw face data, and generation of matched_faces."""
    assert fb.parse_faces(MOCK_JSON['faces']) == PARSED_FACES


@patch('os.access', Mock(return_value=False))
def test_valid_file_path():
    """Test that an invalid file_path is caught."""
    assert not fb.valid_file_path('test_path')


@pytest.fixture
def mock_image():
    """Return a mock camera image."""
    with patch('homeassistant.components.camera.demo.DemoCamera.camera_image',
               return_value=b'Test') as image:
        yield image


async def test_setup_platform(hass):
    """Setup platform with one entity."""
    await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG)
    assert hass.states.get(VALID_ENTITY_ID)


async def test_process_image(hass, mock_image):
    """Test processing of an image."""
    await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG)
    assert hass.states.get(VALID_ENTITY_ID)

    face_events = []

    @callback
    def mock_face_event(event):
        """Mock event."""
        face_events.append(event)

    hass.bus.async_listen('image_processing.detect_face', mock_face_event)

    with requests_mock.Mocker() as mock_req:
        url = "http://{}:{}/facebox/check".format(MOCK_IP, MOCK_PORT)
        mock_req.post(url, json=MOCK_JSON)
        data = {ATTR_ENTITY_ID: VALID_ENTITY_ID}
        await hass.services.async_call(ip.DOMAIN,
                                       ip.SERVICE_SCAN,
                                       service_data=data)
        await hass.async_block_till_done()

    state = hass.states.get(VALID_ENTITY_ID)
    assert state.state == '1'
    assert state.attributes.get('matched_faces') == MATCHED_FACES
    assert state.attributes.get('total_matched_faces') == 1

    PARSED_FACES[0][ATTR_ENTITY_ID] = VALID_ENTITY_ID  # Update.
    assert state.attributes.get('faces') == PARSED_FACES
    assert state.attributes.get(CONF_FRIENDLY_NAME) == 'facebox demo_camera'

    assert len(face_events) == 1
    assert face_events[0].data[ATTR_NAME] == PARSED_FACES[0][ATTR_NAME]
    assert (face_events[0].data[fb.ATTR_CONFIDENCE]
            == PARSED_FACES[0][fb.ATTR_CONFIDENCE])
    assert face_events[0].data[ATTR_ENTITY_ID] == VALID_ENTITY_ID
    assert (face_events[0].data[fb.ATTR_IMAGE_ID] ==
            PARSED_FACES[0][fb.ATTR_IMAGE_ID])
    assert (face_events[0].data[fb.ATTR_BOUNDING_BOX] ==
            PARSED_FACES[0][fb.ATTR_BOUNDING_BOX])


async def test_connection_error(hass, mock_image):
    """Test connection error."""
    await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG)
    assert hass.states.get(VALID_ENTITY_ID)

    with requests_mock.Mocker() as mock_req:
        url = "http://{}:{}/facebox/check".format(MOCK_IP, MOCK_PORT)
        mock_req.register_uri(
            'POST', url, exc=requests.exceptions.ConnectTimeout)
        data = {ATTR_ENTITY_ID: VALID_ENTITY_ID}
        await hass.services.async_call(ip.DOMAIN,
                                       ip.SERVICE_SCAN,
                                       service_data=data)
        await hass.async_block_till_done()

    state = hass.states.get(VALID_ENTITY_ID)
    assert state.state == STATE_UNKNOWN
    assert state.attributes.get('faces') == []
    assert state.attributes.get('matched_faces') == {}


async def test_teach_service(hass, mock_image, mock_isfile, mock_open_file):
    """Test teaching of facebox."""
    await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG)
    assert hass.states.get(VALID_ENTITY_ID)

    teach_events = []

    @callback
    def mock_teach_event(event):
        """Mock event."""
        teach_events.append(event)

    hass.bus.async_listen(
        'image_processing.teach_classifier', mock_teach_event)

    # Patch out 'is_allowed_path' as the mock files aren't allowed
    hass.config.is_allowed_path = Mock(return_value=True)

    with requests_mock.Mocker() as mock_req:
        url = "http://{}:{}/facebox/teach".format(MOCK_IP, MOCK_PORT)
        mock_req.post(url, status_code=200)
        data = {ATTR_ENTITY_ID: VALID_ENTITY_ID,
                ATTR_NAME: MOCK_NAME,
                fb.FILE_PATH: MOCK_FILE_PATH}
        await hass.services.async_call(
            ip.DOMAIN, fb.SERVICE_TEACH_FACE, service_data=data)
        await hass.async_block_till_done()

    assert len(teach_events) == 1
    assert teach_events[0].data[fb.ATTR_CLASSIFIER] == fb.CLASSIFIER
    assert teach_events[0].data[ATTR_NAME] == MOCK_NAME
    assert teach_events[0].data[fb.FILE_PATH] == MOCK_FILE_PATH
    assert teach_events[0].data['success']
    assert not teach_events[0].data['message']

    # Now test the failed teaching.
    with requests_mock.Mocker() as mock_req:
        url = "http://{}:{}/facebox/teach".format(MOCK_IP, MOCK_PORT)
        mock_req.post(url, status_code=400, text=MOCK_ERROR)
        data = {ATTR_ENTITY_ID: VALID_ENTITY_ID,
                ATTR_NAME: MOCK_NAME,
                fb.FILE_PATH: MOCK_FILE_PATH}
        await hass.services.async_call(ip.DOMAIN,
                                       fb.SERVICE_TEACH_FACE,
                                       service_data=data)
        await hass.async_block_till_done()

    assert len(teach_events) == 2
    assert teach_events[1].data[fb.ATTR_CLASSIFIER] == fb.CLASSIFIER
    assert teach_events[1].data[ATTR_NAME] == MOCK_NAME
    assert teach_events[1].data[fb.FILE_PATH] == MOCK_FILE_PATH
    assert not teach_events[1].data['success']
    assert teach_events[1].data['message'] == MOCK_ERROR


async def test_setup_platform_with_name(hass):
    """Setup platform with one entity and a name."""
    named_entity_id = 'image_processing.{}'.format(MOCK_NAME)

    valid_config_named = VALID_CONFIG.copy()
    valid_config_named[ip.DOMAIN][ip.CONF_SOURCE][ip.CONF_NAME] = MOCK_NAME

    await async_setup_component(hass, ip.DOMAIN, valid_config_named)
    assert hass.states.get(named_entity_id)
    state = hass.states.get(named_entity_id)
    assert state.attributes.get(CONF_FRIENDLY_NAME) == MOCK_NAME