import datetime
import hashlib
import os
import time
import urlparse
from cStringIO import StringIO

from werkzeug import http_date
from MoinMoin import wikiutil, config, log
from MoinMoin.Page import Page
from MoinMoin.action.AttachFile import getFilename, remove_attachment, upload_form
import bencode
import pymysql

__author__ = 'murfen'

logging = log.getLogger(__name__)

## cache MySQL connection to xbt tracker database
db = None
torrent_pass_private_key = None

def execute(pagename, request):
    _ = request.getText
    do = request.values.get('do', 'get')
    handler = globals().get('_do_%s' % do)
    if handler:
        msg = handler(pagename, request)
    else:
        msg = _('Unsupported GetTorrent sub-action: %s') % do
    if msg:
        error_msg(pagename, request, msg)

def error_msg(pagename, request, msg):
    msg = wikiutil.escape(msg)
    request.theme.add_msg(msg, "error")
    Page(request, pagename).send_page()

def _access_file(pagename, request):
    """ Check form parameter `target` and return a tuple of
        `(pagename, filename, filepath)` for an existing attachment.

        Return `(pagename, None, None)` if an error occurs.
        This is an exact copy from AttachFile standard module
    """
    _ = request.getText

    error = None
    if not request.values.get('target'):
        error = _("Filename of attachment not specified!")
    else:
        filename = wikiutil.taintfilename(request.values['target'])
        fpath = getFilename(request, pagename, filename)

        if os.path.isfile(fpath):
            return (pagename, filename, fpath)
        error = _("Attachment '%(filename)s' does not exist!") % {'filename': filename}

    error_msg(pagename, request, error)
    return (pagename, None, None)

def _do_get(pagename,request):
    """
    The function is almost the same as in the AttachFile module
    Additionally it filters the file through process_torrent()
    """
    _ = request.getText

    pagename, filename, fpath = _access_file(pagename, request)
    if not request.user.may.read(pagename):
        return _('You are not allowed to get attachments from this page.')
    if not filename:
        return # error msg already sent in _access_file
#    request.user.xbt_id = 1
#    request.user.save()
    
    timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(fpath))
    if_modified = request.if_modified_since
    if if_modified and if_modified >= timestamp:
        request.status_code = 304
    else:
        mt = wikiutil.MimeType(filename=filename)
        content_type = mt.content_type()
        mime_type = mt.mime_type() # should be 'application/x-bittorrent'

        # There is no solution that is compatible to IE except stripping non-ascii chars
        filename_enc = filename.encode(config.charset)

        # for dangerous files (like .html), when we are in danger of cross-site-scripting attacks,
        # we just let the user store them to disk ('attachment').
        # For safe files, we directly show them inline (this also works better for IE).
        dangerous = mime_type in request.cfg.mimetypes_xss_protect
        content_dispo = dangerous and 'attachment' or 'inline'

        now = time.time()
        request.headers['Date'] = http_date(now)
        request.headers['Content-Type'] = content_type
        request.headers['Last-Modified'] = http_date(timestamp)
        request.headers['Expires'] = http_date(now - 365 * 24 * 3600)
#        request.headers['Content-Length'] = os.path.getsize(fpath)
        content_dispo_string = '%s; filename="%s"' % (content_dispo, filename_enc)
        request.headers['Content-Disposition'] = content_dispo_string

        # Process Torrent and send data
        process_torrent(pagename,request,fpath)

def _do_del(pagename, request):
    _ = request.getText

    # change request.action to work around the ticket checking issue
    action = request.action
    request.action = 'AttachFile'
    if not wikiutil.checkTicket(request, request.args.get('ticket', '')):
        return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.del' }
    request.action = action

    pagename, filename, fpath = _access_file(pagename, request)
    if not request.user.may.delete(pagename):
        return _('You are not allowed to delete attachments on this page.')
    if not filename:
        return # error msg already sent in _access_file
    # remove torrent from the DB
    remove_torrent(pagename, request, fpath)
    remove_attachment(request, pagename, filename)

    upload_form(pagename, request, msg=_("Attachment '%(filename)s' deleted.") % {'filename': filename})


def process_torrent(pagename, request, fpath):
    _ = request.getText

    if not db_connect(pagename, request):
        return
    try:
        with open(fpath, 'rb') as f:
            buf = f.read()
    except :
        error_msg(pagename, request, _("Cannot open the attachment"))
        return
    try:
        bt = bencode.bdecode(buf)
    except:
        error_msg(pagename, request, _("Bad torrent file"))
        return
    # force private flag
    try:
        xbt_force_private = request.cfg.xbt_force_private
    except AttributeError:
        xbt_force_private = False
    if xbt_force_private:
        bt['info']['private'] = 1
    info_hash_obj = hashlib.sha1(bencode.bencode(bt['info']))

    # get xbt_users record
    user_record = _get_xbt_user(pagename,request)
    if not user_record:
        return
    uid, torrent_pass, torrent_pass_version, downloaded, uploaded = user_record

    # write the new file to xbt_files table
    sha = info_hash_obj.hexdigest()
    c = db.cursor()
    c.execute("SELECT flags FROM xbt_files WHERE info_hash=UNHEX(%s)", (sha,))
    flag = c.fetchone()
    if flag is None:
        c.execute("INSERT INTO xbt_files(info_hash, mtime, ctime) VALUES(UNHEX(%s), UNIX_TIMESTAMP(), UNIX_TIMESTAMP())",
            (sha,))
        db.commit()
    elif flag[0] % 2 :
        error_msg(pagename, request, _("Torrent is marked for deletion!"))
        return

    # get a secret key from xbt_config
    global torrent_pass_private_key
    if not torrent_pass_private_key:
        c.execute("select value from xbt_config where name = 'torrent_pass_private_key'")
        try:
            torrent_pass_private_key = c.fetchone()[0]
        except TypeError:
            logging.error("Cannot get torrent_pass_private_key from xbt_config table")
            error_msg(pagename,request,_("Tracker configuration error"))
            return

    # compose the torrent password
    s = "%s %d %d %s" % (torrent_pass_private_key, torrent_pass_version, uid, info_hash_obj.digest())
    sha = hashlib.sha1(s).hexdigest()[0:24]
    pwd = "%08x%s" % (uid,  sha)

    # get xbt tracker host:port setting
    try:
        xbt_host = request.cfg.xbt_host
    except AttributeError:
        logging.error("Tracker host parameter xbt_host is not set in wiki config")
        error_msg(pagename, request, _("Wiki configuration error"))
        return

    # set the proper URL in the torrent file
    bt['announce'] = 'http://%s/%s/announce' % (xbt_host, pwd)
    # remove other trackers
    if bt.has_key('announce-list'):
        del bt['announce-list']
    # update comment to current page full address
    url_parts = urlparse.urlsplit(request.url)
    comment = urlparse.urlunsplit((url_parts.scheme, url_parts.netloc, request.page.url(request), '', ''))
    bt['comment'] = comment.encode('utf-8')
    buf = bencode.bencode(bt)
    request.headers['Content-Length'] = len(buf)

    request.send_file(StringIO(buf))

def remove_torrent(pagename, request, fpath):
    _ = request.getText

    if not db_connect(pagename, request):
        return
    try:
        with open(fpath, 'rb') as f:
            buf = f.read()
        bt = bencode.bdecode(buf)
    except:
        return
    # force private flag
    try:
        xbt_force_private = request.cfg.xbt_force_private
    except AttributeError:
        xbt_force_private = False
    if xbt_force_private:
        bt['info']['private'] = 1
    info_hash = hashlib.sha1(bencode.bencode(bt['info'])).hexdigest()
    c = db.cursor()
    try:
        c.execute("update xbt_files set flags = 1 where info_hash = UNHEX(%s)", (info_hash, ))
        db.commit()
    except :
        pass # ignore all database errors

def db_connect(pagename, request):
    _ = request.getText

    global db
    if not db:
        try:
            db = pymysql.connect(**request.cfg.xbt_mysqldb)
        except (AttributeError, pymysql.DatabaseError),err:
            logging.error("Tracker DB connection error: %s" % err)
            error_msg(pagename, request, _("Cannot connect to the tracker database"))
            return False
    else:
        logging.debug("Greate! Reusing MySQL DB connection!")
    return True


def _get_xbt_user(pagename, request):
    """
    Get xbt_users table fields for current user identified by torrent_pass=request.user.id
    Create a new  xbt_users record if it does not exists.
    returned fields are: (uid, torrent_pass, torrent_pass_version, downloaded, uploaded)
    """
    _ = request.getText
    if not request.user.valid or not request.user.name:
        error_msg(pagename, request, _("Anonymous user is not allowed to get a torrent"))
        return False

    c = db.cursor()
    c.execute("SELECT uid, torrent_pass, torrent_pass_version, downloaded, uploaded FROM xbt_users WHERE torrent_pass = %s",
        (request.user.id,)) #rowcount =0
    rec = c.fetchone()
    if not rec:
        # insert a new user
        c.execute("INSERT INTO xbt_users(torrent_pass) VALUES(%s)", (request.user.id))
        c.execute("SELECT uid, torrent_pass, torrent_pass_version, downloaded, uploaded FROM xbt_users WHERE torrent_pass = %s",
            (request.user.id,)) #rowcount =0
        rec = c.fetchone()
        db.commit()
    try:
        uid = int(request.user.xbt_id)
    except AttributeError:
        uid = None
    if not uid or uid != rec[0]:
        request.user.xbt_id = rec[0]
        request.user.save()
    return rec
