import datetime as dt
import email
import json
import re
import subprocess
import sys
import time
import uuid
from email.utils import formatdate
from pathlib import Path
from unittest import mock

import pytest

root = (Path(__file__).parent / '..').resolve()
sys.path.insert(0, str(root))

users = []
test1 = None
test2 = None
con_local = None
con_gmail = None


@pytest.fixture(scope='session', autouse=True)
def init(request):
    from mailur import local

    for i in range(len(request.session.items)):
        users.append([
            'test1_%s' % uuid.uuid4().hex,
            'test2_%s' % uuid.uuid4().hex
        ])

    users_str = ' '.join(sum(users, []))
    subprocess.call('''
    path=/home/vmail/test
    rm -rf $path
    mkdir -p $path
    chown vmail:vmail $path
    names="%s" home=$path append=1 bin/install-users
    systemctl restart dovecot
    ''' % users_str, shell=True, cwd=root)

    # try to connect to dovecot
    for i in range(5):
        try:
            username, pwd = local.master_login(username=users[0][0])
            con = local.connect(username, pwd)
            con.select(local.SRC)
            return
        except Exception as e:
            err = e
            time.sleep(1)
            print('Another try to connect to dovecot: %s' % err)
    raise err


@pytest.fixture
def patch_conf(patch):
    conf = {'USER': test1, 'USE_PROXY': True}
    with patch.dict('mailur.conf', conf):
        yield


@pytest.fixture(autouse=True)
def setup(new_users, gm_client, sendmail, patch_conf):
    from mailur import imap, local, remote

    global con_local, con_gmail

    con_local = local.client(None)
    con_gmail = local.connect(*local.master_login(username=test2))

    remote.data_account({
        'username': 'test',
        'password': 'test',
        'imap_host': 'imap.gmail.com',
        'smtp_host': 'smtp.gmail.com',
    })

    yield

    con_local.logout()
    con_gmail.logout()
    imap.clean_pool(test1)
    imap.clean_pool(test2)


@pytest.fixture
def sendmail(patch):
    with patch('mailur.remote.smtplib.SMTP.sendmail') as m:
        with patch('mailur.remote.smtplib.SMTP.login'):
            yield m


@pytest.fixture
def new_users():
    global test1, test2

    test1, test2 = users.pop()


@pytest.fixture
def load_file():
    def inner(name, charset=None):
        txt = (root / 'tests/files' / name).read_bytes()
        return txt.decode().encode(charset) if charset else txt
    return inner


@pytest.fixture
def load_email(gm_client, load_file, latest):
    def inner(name, charset=None, **opt):
        gm_client.add_emails([{'raw': load_file(name, charset=charset)}])
        return latest(**opt)
    return inner


class Some(object):
    """A helper object that compares equal to everything."""

    def __eq__(self, other):
        self.value = other
        return True

    def __ne__(self, other):
        self.value = other
        return False

    def __getitem__(self, name):
        return self.value[name]

    def __repr__(self):
        return '<ANY>'


@pytest.fixture
def some():
    return Some()


@pytest.fixture
def raises():
    return pytest.raises


@pytest.fixture
def patch():
    return mock.patch


@pytest.fixture
def call():
    return mock.call


def gm_fake():
    from mailur import local

    def uid(name, *a, **kw):
        func = getattr(gm_client, 'fake_%s' % name.lower(), None)
        if func:
            return func(con, *a, **kw)
        responces = getattr(gm_client, name.lower(), None)
        if responces:
            return responces.pop()
        return con._uid(name, *a, **kw)

    def xlist(*a, **kw):
        responces = getattr(gm_client, 'list', None)
        if responces:
            return responces.pop()
        return 'OK', [
            b'(\\HasNoChildren \\All) "/" mlr',
            b'(\\HasNoChildren \\Junk) "/" mlr/All',
            b'(\\HasNoChildren \\Trash) "/" mlr/All',
            b'(\\HasNoChildren \\Draft) "/" mlr/All',
        ]

    con = local.connect(*local.master_login(username=test2))

    con._uid = con.uid
    con.uid = uid
    con._list = con.list
    con.list = xlist
    return con


@pytest.fixture
def gm_client():
    from mailur import local, remote, message

    remote.SKIP_DRAFTS = False

    def add_email(item, tag):
        gm_client.uid += 1
        uid = gm_client.uid
        gid = item.get('gid', 100 * uid)
        raw = item.get('raw')
        if raw:
            msg = raw
            date = gm_client.time + uid
        else:
            txt = item.get('txt', '42')
            msg = message.binary(txt)

            subj = item.get('subj')
            if 'subj' not in item:
                subj = 'Subj %s' % uid
            msg.add_header('Subject', subj)

            date = item.get('date')
            if not date:
                date = gm_client.time + uid
            msg.add_header('Date', formatdate(date, usegmt=True))

            mid = item.get('mid')
            if not mid:
                mid = '<%s@mlr>' % uid
            msg.add_header('Message-ID', mid)

            draft_id = item.get('draft_id')
            if draft_id:
                msg.add_header('X-Draft-ID', '<%s>' % draft_id)

            in_reply_to = item.get('in_reply_to')
            if in_reply_to:
                msg.add_header('In-Reply-To', in_reply_to)
            refs = item.get('refs')
            if refs:
                msg.add_header('References', refs)
            fr = item.get('from')
            if fr:
                msg.add_header('From', fr)
            to = item.get('to')
            if to:
                msg.add_header('To', to)

            msg = msg.as_bytes()

        folder = local.SRC if tag == '\\All' else local.ALL
        res = con_gmail.append(folder, item.get('flags'), None, msg)
        if res[0] != 'OK':
            raise Exception(res)

        arrived = dt.datetime.fromtimestamp(date)
        arrived = arrived.strftime('%d-%b-%Y %H:%M:%S %z').encode()
        flags = item.get('flags', '').encode()
        labels = item.get('labels', '').encode()

        gm_client.fetch[1][1].append(
            (b'1 (X-GM-MSGID %d UID %d )' % (gid, uid))
        )
        gm_client.fetch[0][1].extend([
            (
                b'1 (X-GM-MSGID %d X-GM-THRID %d X-GM-LABELS (%s) UID %d '
                b'INTERNALDATE "%s" FLAGS (%s) '
                b'BODY[] {%d}'
                % (gid, gid, labels, uid, arrived, flags, len(msg)),
                msg
            ),
            b')'
        ])

    def add_emails(items=None, *, tag='\\All', fetch=True, parse=True):
        if items is None:
            items = [{}]
        gm_client.fetch = [('OK', []), ('OK', [])]
        for item in items:
            add_email(item, tag)
        if fetch:
            remote.fetch_folder(tag=tag)
        if parse:
            local.parse()

    gm_client.add_emails = add_emails
    gm_client.uid = 100
    gm_client.time = time.time() - 36000

    with mock.patch('mailur.remote.connect', gm_fake):
        yield gm_client


def _msgs(box=None, uids='1:*', *, parsed=False, raw=False, policy=None):
    from mailur import local, message

    def flags(m):
        res = re.search(r'FLAGS \(([^)]*)\)', m).group(1).split()
        if '\\Recent' in res:
            res.remove('\\Recent')
        return ' '.join(res)

    def msg(res):
        msg = {
            'uid': re.search(r'UID (\d+)', res[0].decode()).group(1),
            'flags': flags(res[0].decode()),
        }
        if parsed:
            body = email.message_from_bytes(res[1], policy=policy)
            parts = [p.get_payload() for p in body.get_payload()]
            txt = [p.get_payload() for p in parts[1]]
            msg['meta'] = json.loads(parts[0])
            msg['body'] = txt[0]
            msg['body_txt'] = txt[1] if len(txt) > 1 else None
            msg['body_end'] = parts[2] if len(parts) > 2 else None
            msg['body_full'] = body
            msg['raw'] = res[1]
        else:
            body = res[1]
            if not raw:
                body = email.message_from_bytes(res[1], policy=policy)
            msg['body'] = body

        return msg

    policy = policy if policy else message.policy
    con_local.select(box or local.ALL)
    res = con_local.fetch(uids, '(uid flags body[])')
    return [msg(res[i]) for i in range(0, len(res), 2)]


@pytest.fixture
def msgs():
    return _msgs


@pytest.fixture
def latest():
    def inner(box=None, *, parsed=False, raw=False, policy=None):
        return _msgs(box, '*', parsed=parsed, raw=raw, policy=policy)[0]
    return inner


@pytest.fixture
def web():
    from webtest import TestApp
    from mailur.web import app, assets, themes

    app.catchall = False

    class Wrapper(TestApp):
        def search(self, data, status=200):
            return self.post_json('/search', data, status=status).json

        def flag(self, data, status=200):
            return self.post_json('/msgs/flag', data, status=status)

        def body(self, uid, fix_privacy=True):
            data = {'uids': [uid], 'fix_privacy': fix_privacy}
            res = self.post_json('/msgs/body', data, status=200).json
            return res[uid]

    if not assets.exists():
        assets.mkdir()
        for i in themes():
            filename = 'theme-%s.css' % i
            (assets / filename).write_text('')
        for filename in ('login.js', 'index.js'):
            (assets / filename).write_text('')
    return Wrapper(app)


@pytest.fixture
def login(web):
    def inner(username=test1, password='demo', tz='Asia/Singapore'):
        params = {'username': username, 'password': password, 'timezone': tz}
        web.post_json('/login', params, status=200)
        return web

    inner.user1 = test1
    inner.user2 = test2
    return inner