import json
import os

from flask import Flask, render_template, request, Response, redirect, url_for
from flask_mail import Mail, Message
from flask_bootstrap import Bootstrap
from flask_wtf import Form
from wtforms import StringField, SubmitField
from wtforms.widgets import TextInput
from wtforms.validators import Required

import logging
from logging.handlers import RotatingFileHandler
from logging import Formatter

from rq import Queue
from rq.job import Job
from worker import conn

try:
    import configparser
except ImportError:
    import ConfigParser as configparser
# from pyfaup.faup import Faup
from .proxied import ReverseProxied
from url_abuse_async import is_valid_url, url_list, dns_resolve, phish_query, psslcircl, \
    vt_query_url, gsb_query, urlquery_query, sphinxsearch, whois, pdnscircl, bgpranking, \
    cached, get_mail_sent, set_mail_sent, get_submissions, eupi

config_path = 'config.ini'


class AngularTextInput(TextInput):

    def __call__(self, field, **kwargs):
        kwargs['ng-model'] = 'input_url'
        return super(AngularTextInput, self).__call__(field, **kwargs)


class URLForm(Form):
    url = StringField('URL Field',
                      description='Enter the URL you want to lookup here.',
                      validators=[Required()], widget=AngularTextInput())

    submit_button = SubmitField('Run lookup')


def make_dict(parser, section):
    to_return = {}
    entries = parser.items(section)
    for k, v in entries:
        to_return[k] = v.split(',')
    return to_return


def prepare_auth():
    if not os.path.exists('users.key'):
        return None
    to_return = {}
    with open('users.key', 'r') as f:
        for l in f:
            l = l.strip()
            user, password = l.split('=')
            to_return[user] = password
    return to_return


def create_app(configfile=None):
    app = Flask(__name__)
    handler = RotatingFileHandler('urlabuse.log', maxBytes=10000, backupCount=5)
    handler.setFormatter(Formatter('%(asctime)s %(message)s'))
    app.wsgi_app = ReverseProxied(app.wsgi_app)
    app.logger.addHandler(handler)
    app.logger.setLevel(logging.INFO)
    Bootstrap(app)
    q = Queue(connection=conn)

    # Mail Config
    app.config['MAIL_SERVER'] = 'localhost'
    app.config['MAIL_PORT'] = 25
    mail = Mail(app)

    app.config['SECRET_KEY'] = 'devkey'
    app.config['BOOTSTRAP_SERVE_LOCAL'] = True
    app.config['configfile'] = config_path

    parser = configparser.SafeConfigParser()
    parser.read(app.config['configfile'])

    replacelist = make_dict(parser, 'replacelist')
    auth_users = prepare_auth()
    ignorelist = [i.strip()
                  for i in parser.get('abuse', 'ignore').split('\n')
                  if len(i.strip()) > 0]
    autosend_threshold = 5

    def _get_user_ip(request):
        ip = request.headers.get('X-Forwarded-For')
        if ip is None:
            ip = request.remote_addr
        return ip

    @app.route('/', methods=['GET', 'POST'])
    def index():
        form = URLForm()
        return render_template('index.html', form=form)

    @app.route('/urlreport', methods=['GET'])
    def url_report():
        return render_template('url-report.html')

    @app.errorhandler(404)
    def page_not_found(e):
        ip = request.headers.get('X-Forwarded-For')
        if ip is None:
            ip = request.remote_addr
        if request.path != '/_result/':
            app.logger.info('404 of {} on {}'.format(ip, request.path))
        return render_template('404.html'), 404

    def authenticate():
        """Sends a 401 response that enables basic auth"""
        return Response('Could not verify your access level for that URL.\n'
                        'You have to login with proper credentials', 401,
                        {'WWW-Authenticate': 'Basic realm="Login Required"'})

    def check_auth(username, password):
        """This function is called to check if a username /
        password combination is valid.
        """
        if auth_users is None:
            return False
        else:
            db_pass = auth_users.get(username)
            return db_pass == password

    @app.route('/login', methods=['GET', 'POST'])
    def login():
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            return authenticate()
        return redirect(url_for('index'))

    @app.route("/_result/<job_key>", methods=['GET'])
    def check_valid(job_key):
        if job_key is None:
            return json.dumps(None), 200
        job = Job.fetch(job_key, connection=conn)
        if job.is_finished:
            return json.dumps(job.result), 200
        else:
            return json.dumps("Nay!"), 202

    @app.route('/start', methods=['POST'])
    def run_query():
        data = json.loads(request.data)
        url = data["url"]
        ip = _get_user_ip(request)
        app.logger.info('{} {}'.format(ip, url))
        if get_submissions(url) >= autosend_threshold:
            send(url, '', True)
        is_valid = q.enqueue_call(func=is_valid_url, args=(url,), result_ttl=500)
        return is_valid.get_id()

    @app.route('/urls', methods=['POST'])
    def urls():
        data = json.loads(request.data)
        url = data["url"]
        u = q.enqueue_call(func=url_list, args=(url,), result_ttl=500)
        return u.get_id()

    @app.route('/resolve', methods=['POST'])
    def resolve():
        data = json.loads(request.data)
        url = data["url"]
        u = q.enqueue_call(func=dns_resolve, args=(url,), result_ttl=500)
        return u.get_id()

    @app.route('/phishtank', methods=['POST'])
    def phishtank():
        data = json.loads(request.data)
        if not os.path.exists('phishtank.key'):
            return None
        url = parser.get("PHISHTANK", "url")
        key = open('phishtank.key', 'r').readline().strip()
        query = data["query"]
        u = q.enqueue_call(func=phish_query, args=(url, key, query,), result_ttl=500)
        return u.get_id()

    @app.route('/virustotal_report', methods=['POST'])
    def vt():
        data = json.loads(request.data)
        if not os.path.exists('virustotal.key'):
            return None
        url = parser.get("VIRUSTOTAL", "url_report")
        url_up = parser.get("VIRUSTOTAL", "url_upload")
        key = open('virustotal.key', 'r').readline().strip()
        query = data["query"]
        u = q.enqueue_call(func=vt_query_url, args=(url, url_up, key, query,), result_ttl=500)
        return u.get_id()

    @app.route('/googlesafebrowsing', methods=['POST'])
    def gsb():
        data = json.loads(request.data)
        if not os.path.exists('googlesafebrowsing.key'):
            return None
        url = parser.get("GOOGLESAFEBROWSING", "url")
        key = open('googlesafebrowsing.key', 'r').readline().strip()
        url = url.format(key)
        query = data["query"]
        u = q.enqueue_call(func=gsb_query, args=(url, query,), result_ttl=500)
        return u.get_id()

    @app.route('/urlquery', methods=['POST'])
    def urlquery():
        data = json.loads(request.data)
        if not os.path.exists('urlquery.key'):
            return None
        url = parser.get("URLQUERY", "url")
        key = open('urlquery.key', 'r').readline().strip()
        query = data["query"]
        u = q.enqueue_call(func=urlquery_query, args=(url, key, query,), result_ttl=500)
        return u.get_id()

    @app.route('/ticket', methods=['POST'])
    def ticket():
        if not request.authorization:
            return ''
        data = json.loads(request.data)
        server = parser.get("SPHINX", "server")
        port = int(parser.get("SPHINX", "port"))
        url = parser.get("ITS", "url")
        query = data["query"]
        u = q.enqueue_call(func=sphinxsearch, args=(server, port, url, query,),
                           result_ttl=500)
        return u.get_id()

    @app.route('/whois', methods=['POST'])
    def whoismail():
        if not request.authorization:
            return ''
        server = parser.get("WHOIS", "server")
        port = parser.getint("WHOIS", "port")
        data = json.loads(request.data)
        query = data["query"]
        u = q.enqueue_call(func=whois, args=(server, port, query, ignorelist, replacelist),
                           result_ttl=500)
        return u.get_id()

    @app.route('/eupi', methods=['POST'])
    def eu():
        data = json.loads(request.data)
        if not os.path.exists('eupi.key'):
            return None
        url = parser.get("EUPI", "url")
        key = open('eupi.key', 'r').readline().strip()
        query = data["query"]
        u = q.enqueue_call(func=eupi, args=(url, key, query,), result_ttl=500)
        return u.get_id()

    @app.route('/pdnscircl', methods=['POST'])
    def dnscircl():
        url = parser.get("PDNS_CIRCL", "url")
        user, password = open('pdnscircl.key', 'r').readlines()
        data = json.loads(request.data)
        query = data["query"]
        u = q.enqueue_call(func=pdnscircl, args=(url, user.strip(), password.strip(),
                                                 query,), result_ttl=500)
        return u.get_id()

    @app.route('/bgpranking', methods=['POST'])
    def bgpr():
        data = json.loads(request.data)
        query = data["query"]
        u = q.enqueue_call(func=bgpranking, args=(query,), result_ttl=500)
        return u.get_id()

    @app.route('/psslcircl', methods=['POST'])
    def sslcircl():
        url = parser.get("PSSL_CIRCL", "url")
        user, password = open('psslcircl.key', 'r').readlines()
        data = json.loads(request.data)
        query = data["query"]
        u = q.enqueue_call(func=psslcircl, args=(url, user.strip(), password.strip(),
                                                 query,), result_ttl=500)
        return u.get_id()

    @app.route('/get_cache', methods=['POST'])
    def get_cache():
        data = json.loads(request.data)
        url = data["query"]
        data = cached(url)
        dumped = json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))
        return dumped

    def digest(data):
        to_return = ''
        all_mails = set()
        for entry in data:
            for url, info in list(entry.items()):
                to_return += '\n{}\n'.format(url)
                if info.get('whois'):
                    all_mails.update(info.get('whois'))
                    to_return += '\tContacts: {}\n'.format(', '.join(info.get('whois')))
                if info.get('vt') and len(info.get('vt')) == 4:
                    vtstuff = info.get('vt')
                    to_return += '\t{} out of {} positive detections in VT - {}\n'.format(
                        vtstuff[2], vtstuff[3], vtstuff[1])
                if info.get('gsb'):
                    to_return += '\tKnown as malicious on Google Safe Browsing: {}\n'.format(info.get('gsb'))
                if info.get('phishtank'):
                    to_return += '\tKnown as malicious on PhishTank\n'
                if info.get('dns'):
                    ipv4, ipv6 = info.get('dns')
                    if ipv4 is not None:
                        for ip in ipv4:
                            to_return += '\t' + ip + '\n'
                            data = info[ip]
                            if data.get('bgp'):
                                to_return += '\t\t(PTR: {}) is announced by {} ({}).\n'.format(*(data.get('bgp')[:3]))
                            if data.get('whois'):
                                all_mails.update(data.get('whois'))
                                to_return += '\t\tContacts: {}\n'.format(', '.join(data.get('whois')))
                    if ipv6 is not None:
                        for ip in ipv6:
                            to_return += '\t' + ip + '\n'
                            data = info[ip]
                            if data.get('whois'):
                                all_mails.update(data.get('whois'))
                                to_return += '\t\tContacts: {}\n'.format(', '.join(data.get('whois')))
            to_return += '\tAll contacts: {}\n'.format(', '.join(all_mails))
        return to_return

    def send(url, ip='', autosend=False):
        if not get_mail_sent(url):
            set_mail_sent(url)
            data = cached(url)
            if not autosend:
                subject = 'URL Abuse report from ' + ip
            else:
                subject = 'URL Abuse report sent automatically'
            msg = Message(subject, sender='[email protected]', recipients=["[email protected]"])
            msg.body = digest(data)
            msg.body += '\n\n'
            msg.body += json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))
            mail.send(msg)

    @app.route('/submit', methods=['POST'])
    def send_mail():
        data = json.loads(request.data)
        url = data["url"]
        if not get_mail_sent(url):
            ip = _get_user_ip(request)
            send(url, ip)
        return redirect(url_for('index'))

    return app