"""Tests for the client validator."""
import asyncio
from unittest.mock import patch

import pytest

from homeassistant.components.auth import indieauth

from tests.common import mock_coro
from tests.test_util.aiohttp import AiohttpClientMocker


@pytest.fixture
def mock_session():
    """Mock aiohttp.ClientSession."""
    mocker = AiohttpClientMocker()

    with patch('aiohttp.ClientSession',
               side_effect=lambda *args, **kwargs:
                   mocker.create_session(asyncio.get_event_loop())):
        yield mocker


def test_client_id_scheme():
    """Test we enforce valid scheme."""
    assert indieauth._parse_client_id('http://ex.com/')
    assert indieauth._parse_client_id('https://ex.com/')

    with pytest.raises(ValueError):
        indieauth._parse_client_id('ftp://ex.com')


def test_client_id_path():
    """Test we enforce valid path."""
    assert indieauth._parse_client_id('http://ex.com').path == '/'
    assert indieauth._parse_client_id('http://ex.com/hello').path == '/hello'
    assert indieauth._parse_client_id(
        'http://ex.com/hello/.world').path == '/hello/.world'
    assert indieauth._parse_client_id(
        'http://ex.com/hello./.world').path == '/hello./.world'

    with pytest.raises(ValueError):
        indieauth._parse_client_id('http://ex.com/.')

    with pytest.raises(ValueError):
        indieauth._parse_client_id('http://ex.com/hello/./yo')

    with pytest.raises(ValueError):
        indieauth._parse_client_id('http://ex.com/hello/../yo')


def test_client_id_fragment():
    """Test we enforce valid fragment."""
    with pytest.raises(ValueError):
        indieauth._parse_client_id('http://ex.com/#yoo')


def test_client_id_user_pass():
    """Test we enforce valid username/password."""
    with pytest.raises(ValueError):
        indieauth._parse_client_id('http://user@ex.com/')

    with pytest.raises(ValueError):
        indieauth._parse_client_id('http://user:pass@ex.com/')


def test_client_id_hostname():
    """Test we enforce valid hostname."""
    assert indieauth._parse_client_id('http://www.home-assistant.io/')
    assert indieauth._parse_client_id('http://[::1]')
    assert indieauth._parse_client_id('http://127.0.0.1')
    assert indieauth._parse_client_id('http://10.0.0.0')
    assert indieauth._parse_client_id('http://10.255.255.255')
    assert indieauth._parse_client_id('http://172.16.0.0')
    assert indieauth._parse_client_id('http://172.31.255.255')
    assert indieauth._parse_client_id('http://192.168.0.0')
    assert indieauth._parse_client_id('http://192.168.255.255')

    with pytest.raises(ValueError):
        assert indieauth._parse_client_id('http://255.255.255.255/')
    with pytest.raises(ValueError):
        assert indieauth._parse_client_id('http://11.0.0.0/')
    with pytest.raises(ValueError):
        assert indieauth._parse_client_id('http://172.32.0.0/')
    with pytest.raises(ValueError):
        assert indieauth._parse_client_id('http://192.167.0.0/')


def test_parse_url_lowercase_host():
    """Test we update empty paths."""
    assert indieauth._parse_url('http://ex.com/hello').path == '/hello'
    assert indieauth._parse_url('http://EX.COM/hello').hostname == 'ex.com'

    parts = indieauth._parse_url('http://EX.COM:123/HELLO')
    assert parts.netloc == 'ex.com:123'
    assert parts.path == '/HELLO'


def test_parse_url_path():
    """Test we update empty paths."""
    assert indieauth._parse_url('http://ex.com').path == '/'


async def test_verify_redirect_uri():
    """Test that we verify redirect uri correctly."""
    assert await indieauth.verify_redirect_uri(
        None,
        'http://ex.com',
        'http://ex.com/callback'
    )

    with patch.object(indieauth, 'fetch_redirect_uris',
                      side_effect=lambda *_: mock_coro([])):
        # Different domain
        assert not await indieauth.verify_redirect_uri(
            None,
            'http://ex.com',
            'http://different.com/callback'
        )

        # Different scheme
        assert not await indieauth.verify_redirect_uri(
            None,
            'http://ex.com',
            'https://ex.com/callback'
        )

        # Different subdomain
        assert not await indieauth.verify_redirect_uri(
            None,
            'https://sub1.ex.com',
            'https://sub2.ex.com/callback'
        )


async def test_find_link_tag(hass, mock_session):
    """Test finding link tag."""
    mock_session.get("http://127.0.0.1:8000", text="""
<!doctype html>
<html>
  <head>
    <link rel="redirect_uri" href="hass://oauth2_redirect">
    <link rel="other_value" href="hass://oauth2_redirect">
    <link rel="redirect_uri" href="/beer">
  </head>
  ...
</html>
""")
    redirect_uris = await indieauth.fetch_redirect_uris(
        hass, "http://127.0.0.1:8000")

    assert redirect_uris == [
        "hass://oauth2_redirect",
        "http://127.0.0.1:8000/beer",
    ]


async def test_find_link_tag_max_size(hass, mock_session):
    """Test finding link tag."""
    text = ''.join([
        '<link rel="redirect_uri" href="/wine">',
        ("0" * 1024 * 10),
        '<link rel="redirect_uri" href="/beer">',
    ])
    mock_session.get("http://127.0.0.1:8000", text=text)
    redirect_uris = await indieauth.fetch_redirect_uris(
        hass, "http://127.0.0.1:8000")

    assert redirect_uris == ["http://127.0.0.1:8000/wine"]