import json
from pathlib import Path

import pytest
from aiohttp import hdrs, web
from aiohttp.test_utils import make_mocked_request as make_request
from yarl import URL

from aiohttp_apiset.compat import MatchInfoError
from aiohttp_apiset.dispatcher import (ContentReceiver, Location, Route,
                                       TreeResource, TreeUrlDispatcher)


def handler(request):
    return web.HTTPOk()


@pytest.fixture
def dispatcher():
    d = TreeUrlDispatcher()
    location = d.add_resource('/', name='root')
    location.add_route('GET', handler)
    d.add_route('GET', '/api/', handler)
    d.add_route('GET', '/api', handler)
    d.add_route('*', '/api/1/pets', handler)
    d.add_route('GET', '/api/1/pet/{id}', handler, name='pet')
    d.add_route('GET', '/api/1/pet/{id}/', handler)
    return d


def test_create(dispatcher: TreeUrlDispatcher):
    assert dispatcher.tree_resource._location._subs


def test_add_resource(dispatcher: TreeUrlDispatcher):
    location = dispatcher.add_resource('/api/1/dogs', name='dogs')
    assert str(location.url_for()) == '/api/1/dogs'


async def test_simple(dispatcher: TreeUrlDispatcher, request: web.Request):
    request = make_request('GET', '/api/1/pet/1')
    md = await dispatcher.resolve(request)
    assert md == {'id': '1'}

    request = make_request('GET', '/api/1/pets')
    md = await dispatcher.resolve(request)
    assert not md


async def test_multisubs(dispatcher: TreeUrlDispatcher):
    url = r'/api/1/host/{host}/eth{num}/{ip:[.\d]+}/'
    dispatcher.add_route('GET', url, handler)

    request = make_request('GET', '/api/1/host/myhost/eth0/127.0.0.1/')
    md = await dispatcher.resolve(request)
    assert len(md)
    assert 'ip' in md
    assert 'num' in md
    assert 'host' in md

    request = make_request('GET', '/api/1/host/myhost/eth0/127.0.0.1')
    md = await dispatcher.resolve(request)
    assert isinstance(md, MatchInfoError)


def test_url(dispatcher: TreeUrlDispatcher):
    location = dispatcher['root']
    assert 'path' in location.get_info()
    route = location._routes['GET']
    route.set_info(x=1)
    assert route.get_info().get('x') == 1

    location = dispatcher['pet']
    assert location.url(parts={'id': 1}) == '/api/1/pet/1'
    assert location.url_for(id=1) == URL('/api/1/pet/1')
    assert repr(location)

    route = location._routes['GET']
    assert route.url(parts={'id': 1}) == '/api/1/pet/1'
    assert route.url_for(id=1) == URL('/api/1/pet/1')
    assert repr(route)
    assert route.name
    assert 'formatter' in route.get_info()

    assert dispatcher.tree_resource.url_for() == URL('/')
    assert dispatcher.tree_resource.url(query={'q': 1}) == '/?q=1'
    assert repr(dispatcher.tree_resource)
    assert dispatcher.tree_resource.get_info() is not None


@pytest.mark.parametrize('hstr', [
    'tests.conftest.View.retrieve',
    'tests.conftest.SimpleView.get',
    'tests.conftest.SimpleView.post',
])
async def test_import_handler(hstr):
    handler, parameters = Route._import_handler(hstr)
    with pytest.raises(web.HTTPException):
        await handler(**{k: None for k in parameters})


def test_import_one_dot_handler():
    Route._import_handler('asyncio.wait')


def test_import_handler_():
    with pytest.raises(ValueError):
        Route._import_handler('a')
    assert Route._import_handler('aiohttp.web.View.get')


def test_view_locations(dispatcher: TreeUrlDispatcher):
    resources = dispatcher.resources()
    assert list(resources)[0] in resources
    assert len(resources)
    routes = dispatcher.routes()
    assert list(routes)[0] in routes
    assert len(routes)


async def test_static(loop, test_client, mocker):
    f = Path(__file__)
    dispatcher = TreeUrlDispatcher()
    dispatcher.add_static('/static', f.parent, name='static')
    app = web.Application(router=dispatcher, loop=loop)
    client = await test_client(app)

    url = dispatcher['static'].url_for(filename=f.name)
    responce = await client.get(url)
    assert responce.status == 200

    m = mocker.patch('aiohttp_apiset.dispatcher.mimetypes')
    m.guess_type.return_value = None, None
    responce = await client.get(url)
    assert responce.status == 200

    url = dispatcher['static'].url_for(filename='..' + f.name)
    responce = await client.get(url)
    assert responce.status == 403

    url = dispatcher['static'].url_for(filename='/etc/passwd')
    responce = await client.get(url)
    assert responce.status == 404

    url = dispatcher['static'].url_for(filename='1/2/3')
    responce = await client.get(url)
    assert responce.status == 404

    url = dispatcher['static'].url_for(filename='')
    responce = await client.get(url)
    assert responce.status == 404

    url = dispatcher['static'].url_for(filename='data')
    responce = await client.get(url)
    assert responce.status == 404


async def test_static_with_default(loop, test_client):
    f = Path(__file__)
    dispatcher = TreeUrlDispatcher()
    dispatcher.add_static('/static', f.parent, name='static', default=f.name)
    dispatcher.add_static('/static2', f.parent, name='static2', default='1234')
    app = web.Application(router=dispatcher, loop=loop)
    client = await test_client(app)

    url = dispatcher['static'].url_for(filename='1/2/3')
    responce = await client.get(url)
    assert responce.status == 200

    url = dispatcher['static2'].url_for(filename='1/2/3')
    responce = await client.get(url)
    assert responce.status == 404

    url = dispatcher['static'].url_for(filename='')
    responce = await client.get(url)
    assert responce.status == 200

    url = dispatcher['static'].url_for(filename='data')
    responce = await client.get(url)
    assert responce.status == 200


def test_similar_patterns():
    dispatcher = TreeUrlDispatcher()
    dispatcher.add_get('/{a}', handler)
    with pytest.raises(ValueError):
        dispatcher.add_get('/{b}', handler)


def test_treeresource():
    a = TreeResource()
    assert not len(a)
    assert not list(a)


def test_sublocation_notresolved(mocker):
    location = Location(formatter='')
    m, allow = location.resolve(mocker.Mock(), '/not', {})
    assert not m
    assert not allow


def test_novalid_path():
    r = TreeUrlDispatcher()
    with pytest.raises(ValueError):
        r.add_resource('dfsdf')
    with pytest.raises(ValueError):
        r.add_get('dfsdf', None)


async def test_dispatcher_not_resolve():
    r = TreeUrlDispatcher()
    r.add_put('/', handler)
    req = make_request('GET', '/')
    a = await r.resolve(req)
    assert isinstance(a.http_exception, web.HTTPMethodNotAllowed)


async def test_default_options(test_client):
    headers = {
        hdrs.ACCESS_CONTROL_REQUEST_HEADERS: hdrs.AUTHORIZATION}
    request = make_request('OPTIONS', '/', headers=headers)
    router = TreeUrlDispatcher()
    mi = await router.resolve(request)
    assert isinstance(mi, MatchInfoError)

    app = web.Application(router=router)
    router.set_cors(app)
    router.add_get('/', lambda request: web.Response())
    mi = await router.resolve(request)
    assert not isinstance(mi, MatchInfoError)
    client = await test_client(app)
    response = await client.options('/', headers=headers)
    assert response.status == 200
    h = response.headers
    assert h[hdrs.ACCESS_CONTROL_ALLOW_ORIGIN] == '*'
    assert h[hdrs.ACCESS_CONTROL_ALLOW_METHODS] == 'GET'
    assert h[hdrs.ACCESS_CONTROL_ALLOW_HEADERS] == hdrs.AUTHORIZATION


async def test_init():
    r = TreeUrlDispatcher()
    r.add_get('/', 'tests.conftest.ViewWithInit.get')
    req = make_request('GET', '/')
    mi = await r.resolve(req)
    result = await mi.handler(req)
    assert result is req, result


async def test_branch_path():
    r = TreeUrlDispatcher()
    h = 'tests.conftest.ViewWithInit.get'
    r.add_get('/net/ip/', h)
    route = r.add_get('/net/{ip}/host', h)
    req = make_request('GET', '/net/ip/host')
    mi = await r.resolve(req)
    assert mi.route is route


async def test_subrouter():
    request = make_request('GET', '/a/b/c/d')
    router = TreeUrlDispatcher()
    subapp = web.Application(router=router)
    route = subapp.router.add_get('/c/d', handler)
    app = web.Application()
    app.add_subapp('/a/b', subapp)
    m = await app.router.resolve(request)
    assert route == m.route


async def test_superrouter():
    request = make_request('GET', '/a/b/c/d')
    router = TreeUrlDispatcher()
    subapp = web.Application()
    route = subapp.router.add_get('/c/d', handler)
    app = web.Application(router=router)
    app.add_subapp('/a/b', subapp)
    m = await app.router.resolve(request)
    assert route == m.route


async def test_content_receiver():
    cr = ContentReceiver()
    l1 = len(cr)
    assert l1
    mime_json = 'application/json'
    assert mime_json in cr
    cr[None] = cr[mime_json]
    assert len(cr) == l1 + 1
    request = make_request('PUT', '/', headers={'Content-Type': mime_json})
    request._read_bytes = json.dumps(2).encode()
    assert 2 == await cr.receive(request)
    del cr[None]

    cr.freeze()
    with pytest.raises(RuntimeError):
        del cr[mime_json]
    with pytest.raises(RuntimeError):
        cr[None] = None

    request = make_request('PUT', '/', headers={'Content-Type': '1'})
    with pytest.raises(TypeError):
        await cr.receive(request)

    assert list(cr)


async def test_set_content_receiver(loop):
    async def test_receiver(request):
        pass

    r = TreeUrlDispatcher()
    r.set_content_receiver('test', test_receiver)
    r.add_post('/', 'tests.conftest.SimpleView.post')
    req = make_request('POST', '/', headers={'Content-Type': 'test'})
    mi = await r.resolve(req)
    assert mi.route._content_receiver.get('test') is test_receiver


async def test_dynamic_sort():
    r = TreeUrlDispatcher()
    r.add_get('/a/{b}-{c}', handler)
    route = r.add_get('/a/{b}-{c}.jpg', handler)
    req = make_request('GET', '/a/2-3.jpg')
    mi = await r.resolve(req)
    assert mi.route is route