import os
from unittest import TestCase

import webob
from mock import patch, Mock, call

all_tests = False
if os.environ.get('WESGI_ALL_TESTS', 'false').lower() in ('true', '1', 't'):
    all_tests = True

patch_Http = patch('wesgi.Http', mocksignature=True)

def mock_http_request(http, response=None, content=None):
    """Return a httplib2.Http with request() mocked out"""
    http.request = Mock(spec_set=[])
    mock = http.request
    if content is not None or response is not None:
        if content is None:
            content = b''
        if response is None:
            response = Response()
        mock.return_value = response, content

def Response(status=200, headers=None):
    import httplib2
    assert isinstance(status, int)
    d = dict(status=str(status))
    if headers is not None:
        d.update(headers)
    return httplib2.Response(d)

def run_mw(mw, **requestkwargs):
    start_response = Mock()
    request = webob.Request.blank("", **requestkwargs)
    response = mw(request.environ, start_response)
    return b''.join(response)

def make_mw(app=None, **kw):
    status = kw.pop('http_status', 200)
    headers = kw.pop('http_headers', None)
    content = kw.pop('http_content', b'')
    if app is None:
        body = kw.pop('app_body', b'')
        app = make_app(body=body)
    from wesgi import MiddleWare
    mw = MiddleWare(app, **kw)
    mock_http_request(mw.http, Response(status=status, headers=headers), content)
    return mw

def make_app(body=b'', content_type='text/html', status=200):
    def _app(environ, start_response):
        response = webob.Response(body, content_type=content_type)
        response.status = status
        return response(environ, start_response)
    return _app


class TestProcessInclude(TestCase):

    def test_return_none_if_no_match(self):
        mw = make_mw()
        req = webob.Request.blank("")
        mock = mw.http.request
        data = mw._process_include(b'', req)
        self.assertEqual(data, None)
        self.assertFalse(mock.called)
        data = mw._process_include(b'something', req)
        self.assertEqual(data, None)
        self.assertFalse(mock.called)
        data = mw._process_include(b'<html><head></head><body><h1>HI</h1><esi:not_an_include whatever="bobo"/></body></html>', req)
        self.assertEqual(data, None)
        self.assertFalse(mock.called)

    def test_match(self):
        mw = make_mw(http_content=b'<div>example</div>')
        req = webob.Request.blank("")
        data = mw._process_include(b'before<esi:include src="http://www.example.com"/>after', req)
        self.assertEqual(data, b'before<div>example</div>after')
        self.assertEqual(mw.http.request.call_count, 1)
        self.assertEqual(mw.http.request.call_args,
                         call('http://www.example.com', headers={}))
        # onerror="continue" has no effect
        mw.http.request.reset_mock()
        data = mw._process_include(b'before<esi:include src="http://www.example.com" onerror="continue"/>after', req)
        self.assertEqual(data, b'before<div>example</div>after')
        self.assertEqual(mw.http.request.call_count, 1)
        self.assertEqual(mw.http.request.call_args,
                         call('http://www.example.com', headers={}))

    def test_recursive(self):
        from wesgi import RecursionError, AkamaiPolicy
        counter = list(range(10))
        def side_effect(*args, **kwargs):
            if not counter:
                return Response(), b'-last'
            count = counter.pop(0)
            fragment = count + 1 # the nth fragment that we are including
            count += 2 # count starts at 0, but first time we include is level 2
            return (Response(),
                    ('-%s<esi:include src="http://www.example.com/%s"/>' %
                     (count, fragment)).encode('ascii'))
        req = webob.Request.blank("")
        mw = make_mw()
        mock = mw.http.request
        mock.side_effect = side_effect
        data = mw._process_include(b'level-1<esi:include src="http://www.example.com"/>-after', req)
        self.assertEqual(data, b'level-1-2-3-4-5-6-7-8-9-10-11-last-after')
        self.assertEqual(mock.call_count, 11)
        self.assertEqual(mock.call_args,
                         call('http://www.example.com/10', headers={}))
        # Akamai FAQ http://www.akamai.com/dl/technical_publications/esi_faq.pdf
        # claims that they support 5 levels of nested includes. Ours should do the same
        # when using the akami policy
        counter.extend(range(5))
        mw.policy = AkamaiPolicy()
        self.assertRaises(RecursionError, mw._process_include, b'level-1<esi:include src="http://www.example.com"/>-after', req)
        counter.extend(range(4))
        data = mw._process_include(b'level-1<esi:include src="http://www.example.com"/>-after', req)
        self.assertEqual(data, b'level-1-2-3-4-5-last-after')