import cPickle
import email.header
import email.parser
import os
import tempfile
import random
import psycopg2
import pytest
import shutil
import sys
import threading
import urlparse

from wcs import sql

from webtest import TestApp
from quixote import cleanup, get_publisher
from django.conf import settings

import wcs
import wcs.wsgi
from wcs import publisher, compat
from wcs.qommon.http_request import HTTPRequest
from wcs.users import User
from wcs.tracking_code import TrackingCode
import wcs.qommon.emails
import wcs.qommon.sms
import qommon.sms
from qommon.errors import ConnectionError

import wcs.middleware

wcs.middleware.AfterJobsMiddleware.ASYNC = False

class KnownElements(object):
    pickle_app_dir = None
    sql_app_dir = None
    sql_db_name = None

known_elements = KnownElements()

def create_temporary_pub(sql_mode=False):
    if sql_mode is True:
        if pytest.config.getoption('without_postgresql_tests'):
            pytest.skip("unsupported configuration")
            return
    if get_publisher():
        get_publisher().cleanup()
        cleanup()
    if sql_mode is False and known_elements.pickle_app_dir:
        APP_DIR = known_elements.pickle_app_dir
    elif sql_mode is True and known_elements.sql_app_dir:
        APP_DIR = known_elements.sql_app_dir
    else:
        APP_DIR = tempfile.mkdtemp()
        if sql_mode is True:
            known_elements.sql_app_dir = APP_DIR
        elif sql_mode is False:
            known_elements.pickle_app_dir = APP_DIR

    compat.CompatWcsPublisher.APP_DIR = APP_DIR
    compat.CompatWcsPublisher.DATA_DIR = os.path.abspath(
            os.path.join(os.path.dirname(wcs.__file__), '..', 'data'))
    compat.CompatWcsPublisher.cronjobs = None
    pub = compat.CompatWcsPublisher.create_publisher()
    # allow saving the user
    pub.app_dir = os.path.join(APP_DIR, 'example.net')
    pub.site_charset = 'utf-8'

    if sql_mode:
        pub.user_class = sql.SqlUser
        pub.tracking_code_class = sql.TrackingCode
        pub.is_using_postgresql = lambda: True
    else:
        pub.user_class = User
        pub.tracking_code_class = TrackingCode
        pub.is_using_postgresql = lambda: False

    if os.path.exists(os.path.join(pub.app_dir, 'wcs.log')):
        os.unlink(os.path.join(pub.app_dir, 'wcs.log'))

    if os.path.exists(os.path.join(pub.APP_DIR, 'scripts')):
        shutil.rmtree(os.path.join(pub.APP_DIR, 'scripts'))
    if os.path.exists(os.path.join(pub.app_dir, 'scripts')):
        shutil.rmtree(os.path.join(pub.app_dir, 'scripts'))

    if os.path.exists(pub.app_dir):
        pub.cfg = {}
        if sql_mode:
            pub.cfg['postgresql'] = {'database': known_elements.sql_db_name, 'user': os.environ['USER']}
        pub.cfg['misc'] = {'charset': 'utf-8'}
        pub.cfg['language'] = {'language': 'en'}
        pub.write_cfg()
        return pub

    os.mkdir(pub.app_dir)
    fd = file(os.path.join(pub.app_dir, 'site-options.cfg'), 'w')
    fd.write('[wscall-secrets]\n')
    fd.write('idp.example.net = BAR\n')
    fd.write('\n')
    fd.write('[options]\n')
    fd.write('formdef-captcha-option = true\n')
    fd.write('workflow-resubmit-action = true\n')
    if sql_mode:
        fd.write('postgresql = true\n')
        conn = psycopg2.connect(user=os.environ['USER'])
        conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
        cur = conn.cursor()
        dbname = 'wcstests%d' % random.randint(0, 100000)
        known_elements.sql_db_name = dbname
        cur.execute('CREATE DATABASE %s' % dbname)
        cur.close()

        pub.cfg['postgresql'] = {'database': dbname, 'user': os.environ['USER']}
        pub.cfg['misc'] = {'charset': 'utf-8'}
        pub.write_cfg()

        sql.do_user_table()
        sql.do_tracking_code_table()

        conn.close()

    fd.close()

    return pub

def clean_temporary_pub():
    if get_publisher():
        get_publisher().cleanup()
    if known_elements.pickle_app_dir and os.path.exists(known_elements.pickle_app_dir):
        shutil.rmtree(known_elements.pickle_app_dir)
        known_elements.pickle_app_dir = None
    if known_elements.sql_app_dir and os.path.exists(known_elements.sql_app_dir):
        shutil.rmtree(known_elements.sql_app_dir)
        known_elements.sql_app_dir = None
    if known_elements.sql_db_name:
        conn = psycopg2.connect(user=os.environ['USER'])
        conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
        try:
            cur = conn.cursor()
            cur.execute('DROP DATABASE %s' % known_elements.sql_db_name)
            cur.close()
        except psycopg2.Error:
            pass
        known_elements.sql_db_name = None

def get_app(pub, https=False):
    extra_environ = {'HTTP_HOST': 'example.net', 'REMOTE_ADDR': '127.0.0.1'}
    if https:
        settings.SECURE_PROXY_SSL_HEADER = ('HTTPS', 'on')
        extra_environ['HTTPS'] = 'on'
    else:
        extra_environ['HTTPS'] = 'off'
    return TestApp(wcs.wsgi.application, extra_environ=extra_environ)

def login(app, username='admin', password='admin'):
    login_page = app.get('/login/')
    login_form = login_page.forms['login-form']
    login_form['username'] = username
    login_form['password'] = password
    resp = login_form.submit()
    assert resp.status_int == 302
    return app


class EmailsMocking(object):
    def create_smtp_server(self, *args, **kwargs):
        class MockSmtplibSMTP(object):
            def __init__(self, emails):
                self.emails = emails

            def sendmail(self, msg_from, rcpts, msg):
                msg = email.parser.Parser().parsestr(msg)
                subject = email.header.decode_header(msg['Subject'])[0][0]
                if msg.is_multipart():
                    payload = msg.get_payload()[0].get_payload(decode=True)
                else:
                    payload = msg.get_payload(decode=True)
                self.emails[subject] = {
                        'from': msg_from,
                        'to': email.header.decode_header(msg['To'])[0][0],
                        'payload': payload,
                        'msg': msg,
                        }
                self.emails[subject]['email_rcpt'] = rcpts

            def close(self):
                pass

        return MockSmtplibSMTP(self.emails)

    def get(self, subject):
        return self.emails.get(subject)

    def empty(self):
        self.emails.clear()

    def count(self):
        return len(self.emails)

    def __enter__(self):
        self.wcs_create_smtp_server = sys.modules['wcs.qommon.emails'].create_smtp_server
        self.qommon_create_smtp_server = sys.modules['qommon.emails'].create_smtp_server

        sys.modules['wcs.qommon.emails'].create_smtp_server = self.create_smtp_server
        sys.modules['qommon.emails'].create_smtp_server = self.create_smtp_server

        self.emails = {}
        return self

    def __exit__(self, exc_type, exc_value, tb):
        del self.emails
        sys.modules['wcs.qommon.emails'].create_smtp_server = self.wcs_create_smtp_server
        sys.modules['qommon.emails'].create_smtp_server = self.qommon_create_smtp_server


class MockSubstitutionVariables(object):
    def get_substitution_variables(self):
        return {'bar': 'Foobar', 'foo': '1 < 3', 'email': 'sub@localhost',
                'empty': ''}


class HttpRequestsMocking(object):
    def __init__(self):
        self.requests = []

    def __enter__(self):
        import wcs.qommon.misc
        import qommon.misc

        self.wcs_qommon_misc_http_request = wcs.qommon.misc._http_request
        self.qommon_misc_http_request = qommon.misc._http_request

        wcs.qommon.misc._http_request = self.http_request
        qommon.misc._http_request = self.http_request
        return self

    def __exit__(self, exc_type, exc_value, tb):
        import wcs.qommon.misc
        import qommon.misc

        wcs.qommon.misc._http_request = self.wcs_qommon_misc_http_request
        qommon.misc._http_request = self.qommon_misc_http_request

        del self.wcs_qommon_misc_http_request
        del self.qommon_misc_http_request

    def http_request(self, url, method='GET', body=None, headers={},
            cert_file=None, timeout=None, raise_on_http_errors=False):
        self.requests.append(
                {'url': url,
                 'method': method,
                 'body': body,
                 'headers': headers,
                 'timeout': timeout})

        scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
        base_url = urlparse.urlunparse((scheme, netloc, path, '', '', ''))

        status, data, headers = {
            'http://remote.example.net/204': (204, None, None),
            'http://remote.example.net/400-json': (400, '{"err": 1, "err_desc": ":("}', None),
            'http://remote.example.net/404': (404, 'page not found', None),
            'http://remote.example.net/404-json': (404, '{"err": 1}', None),
            'http://remote.example.net/500': (500, 'internal server error', None),
            'http://remote.example.net/json': (200, '{"foo": "bar"}', None),
            'http://remote.example.net/json-err0': (200, '{"data": "foo", "err": 0}', None),
            'http://remote.example.net/json-err1': (200, '{"data": "", "err": 1}', None),
            'http://remote.example.net/json-errstr': (200, '{"data": "", "err": "bug"}', None),
            'http://remote.example.net/json-errheader0': (200, '{"foo": "bar"}',
                                              {'x-error-code': '0'}),
            'http://remote.example.net/json-errheader1': (200, '{"foo": "bar"}',
                                              {'x-error-code': '1'}),
            'http://remote.example.net/json-errheaderstr': (200, '{"foo": "bar"}',
                                              {'x-error-code': 'bug'}),
            'http://remote.example.net/xml': (200, '<?xml version="1.0"><foo/>',
                                              {'content-type': 'text/xml'}),
            'http://remote.example.net/xml-errheader': (200, '<?xml version="1.0"><foo/>',
                                              {'content-type': 'text/xml', 'x-error-code': '1'}),
            'http://remote.example.net/connection-error': (None, None, None),
            'http://authentic.example.net/idp/saml2/metadata': (
                200, open(os.path.join(os.path.dirname(__file__), 'idp_metadata.xml')).read(), None),
            }.get(base_url, (200, '', {}))

        if url.startswith('file://'):
            try:
                status, data = 200, open(url[7:]).read()
            except IOError:
                status = 404

        class FakeResponse(object):
            def __init__(self, status, data, headers):
                self.status_code = status
                self.reason = 'whatever'
                self.data = data
                self.headers = headers or {}
                self.length = len(data or '')

        if status is None:
            raise ConnectionError('error')

        if raise_on_http_errors and not (200 <= status < 300):
            raise ConnectionError('error in HTTP request to (status: %s)' % status)

        return FakeResponse(status, data, headers), status, data, None

    def get_last(self, attribute):
        return self.requests[-1][attribute]

    def empty(self):
        self.requests = []


class SMSMocking(wcs.qommon.sms.MobytSMS):
    def get_sms_class(self, mode):
        if mode == 'none':
            return None
        return self

    def send(self, sender, destinations, text, quality=None):
        self.sms.append({'sender': sender, 'destinations': destinations, 'text': text})

    def get_sms_left(self, type='standard'):
        raise NotImplementedError

    def get_money_left(self):
        raise NotImplementedError

    def __enter__(self):
        self.sms = []
        self.wcs_get_sms_class = wcs.qommon.sms.SMS.get_sms_class
        self.qommon_get_sms_class = qommon.sms.SMS.get_sms_class
        wcs.qommon.sms.SMS.get_sms_class = self.get_sms_class
        qommon.sms.SMS.get_sms_class = self.get_sms_class
        return self

    def __exit__(self, exc_type, exc_value, tb):
        del self.sms
        wcs.qommon.sms.SMS.get_sms_class = self.wcs_get_sms_class
        qommon.sms.SMS.get_sms_class = self.qommon_get_sms_class
