import click
import arrow
from os.path import isfile
from time import sleep
from flask import Flask, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
from redis import Redis
from datetime import datetime, timezone, timedelta
from app import config
from app.library.mattermost import post_webhook


db = SQLAlchemy()

def _setup_db(app: Flask):
    uri = 'postgresql+psycopg2://{user}:{pw}@{host}:{port}/{db}'.format(
        user=config.DB_USER,
        pw=config.DB_PASS,
        host=config.DB_HOST,
        port=config.DB_PORT,
        db=config.DB_NAME
    )
    app.config['SQLALCHEMY_DATABASE_URI'] = uri
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    db = SQLAlchemy(app)

def create_app():
    app = Flask(__name__)
    app.config.from_envvar('FLASK_SECRETS')

    # Setup backends
    _setup_db(app)

    with app.app_context():

        # todo - find a way to move filters into a new file

        # Template filters
        @app.template_filter('pst')
        def pst(s):
            d = arrow.get(s).to('US/Pacific').format('YYYY-MM-DD HH:mm:ss')
            return d

        @app.template_filter('from_atomic_wow')
        def from_atomic(v):
            from app.library.crypto import wownero
            return wownero.as_real(wownero.from_atomic(v))

        @app.template_filter('from_atomic_xmr')
        def from_atomic(v):
            from app.library.crypto import monero
            return monero.as_real(monero.from_atomic(v))

        @app.template_filter('from_atomic_usd')
        def to_ausd(v):
            from app.models import Swap
            return Swap().from_ausd(v)

        @app.template_filter('ts')
        def from_ts(v):
            from datetime import datetime
            return datetime.fromtimestamp(v)

        @app.template_filter('wow_block_explorer')
        def wow_block_explorer(v):
            return f'https://wownero.club/transaction/{v}'

        @app.template_filter('xmr_block_explorer')
        def xmr_block_explorer(v):
            return f'https://www.exploremonero.com/transaction/{v}'

        # todo - find a way to move into a new file
        # CLI
        @app.cli.command('init')
        def init():
            import app.models
            db.create_all()

        @app.cli.command('store')
        def store():
            from app.library.crypto import wow_wallet, xmr_wallet
            a, b = wow_wallet.make_wallet_rpc('store'), xmr_wallet.make_wallet_rpc('store')
            return (a, b)

        @app.cli.command('show')
        @click.argument('swap_id')
        def show_swap(swap_id):
            from pprint import pprint
            from app.models import Swap
            s = Swap.query.get(swap_id)
            if s:
                pprint(s.show_details())
            else:
                print('Swap ID does not exist')

        @app.cli.command('delete')
        @click.argument('swap_id')
        def show_swap(swap_id):
            from app.models import Swap
            s = Swap.query.get(swap_id)
            if s:
                print(s.show_details())
                db.session.delete(s)
                db.session.commit()
                print(f'Swap {s.id} was deleted')
                post_webhook(f'Deleted swap {s.id} from console')
            else:
                print('Swap ID does not exist')

        @app.cli.command('list')
        def list_swaps():
            from app.models import Swap
            s = Swap.query.all()
            for i in s:
                print(i.id)

        @app.cli.command('wow_transfer')
        @click.argument('address')
        @click.argument('amount')
        def wow_transfer(address, amount):
            from app.library.crypto import wow_wallet, wownero
            tx = wow_wallet.transfer(address, wownero.to_atomic(wownero.as_real(amount)))
            post_webhook(f'Sent {amount} WOW to `{address}` from console. Tx ID: `{tx["tx_hash"]}`')
            print(tx)

        @app.cli.command('xmr_transfer')
        @click.argument('address')
        @click.argument('amount')
        def xmr_transfer(address, amount):
            from app.library.crypto import xmr_wallet, monero
            tx = xmr_wallet.transfer(address, monero.to_atomic(monero.as_real(amount)))
            post_webhook(f'Sent {amount} XMR to `{address}` from console. Tx ID: `{tx["tx_hash"]}`')
            print(tx)

        @app.cli.command('process_expired')
        def process_expired():
            from app.models import Swap
            _hours = config.SWAP_EXPIRATION_HOURS
            for swap in Swap.query.filter(Swap.funds_received == False):
                elapsed = swap.hours_elapsed()
                details = swap.show_details()
                if elapsed >= _hours:
                    db.session.delete(swap)
                    db.session.commit()
                    print(f'Swap {swap.id} has not received funds in over {_hours} hours ({elapsed} hours) and was deleted: {details}')
                    post_webhook(f'Deleted swap {swap.id}; {round(elapsed)} hours old with no incoming transfers.')
                    sleep(5)

        @app.cli.command('process_incoming')
        def process_incoming():
            from app.library.crypto import wow_wallet, xmr_wallet
            from app.models import Swap
            for swap in Swap.query.filter(Swap.funds_received == False):
                swap_tx_amounts = list()
                if swap.wow_to_xmr:
                    incoming_wallet = wow_wallet
                else:
                    incoming_wallet = xmr_wallet
                txes = incoming_wallet.incoming_transfers(swap.swap_address_index)
                if 'transfers' in txes:
                    for tx in txes['transfers']:
                        if tx['unlocked']:
                            swap_tx_amounts.append(tx['amount'])
                amount_expected = swap.receive_amount_atomic
                amount_received = sum(swap_tx_amounts)
                if amount_received >= amount_expected:
                    url = url_for('swap.get_swap', swap_id=swap.id, _external=True)
                    msg = f'Funds received for swap [{swap.id}]({url}) ({swap.show_details()["receive"]})'
                    swap.funds_received = True
                    db.session.add(swap)
                    db.session.commit()
                    print(msg)
                    post_webhook(msg)

        @app.cli.command('process_outgoing')
        def process_outgoing():
            from datetime import datetime
            from app.library.crypto import wow_wallet, xmr_wallet, wownero, monero
            from app.models import Swap
            from time import sleep

            for swap in Swap.query.filter(Swap.funds_received == True, Swap.completed == False):
                print(f'Planning to send {swap.show_details()["send"]} for swap {swap.id}')
                sleep(10)
                if swap.wow_to_xmr:
                    outgoing_wallet = xmr_wallet
                    dest_address = swap.return_xmr_address
                    crypto = monero
                else:
                    outgoing_wallet = wow_wallet
                    dest_address = swap.return_wow_address
                    crypto = wownero
                available_funds = outgoing_wallet.balances()[0]
                if available_funds > swap.send_amount_atomic:
                    res = outgoing_wallet.transfer(dest_address, swap.send_amount_atomic)
                    if not 'message' in res:
                        url = url_for('swap.get_swap', swap_id=swap.id, _external=True)
                        res['address'] = dest_address
                        msg = 'Sent {amount} {coin} to return address for swap [{id}]({url}): `{tx_id}` - `{res}`'.format(
                            amount=crypto.from_atomic(swap.send_amount_atomic),
                            coin=swap.send_coin().upper(),
                            id=swap.id,
                            tx_id=res['tx_hash'],
                            url=url,
                            res=res
                        )
                        print(msg)
                        post_webhook(msg)
                        swap.completed = True
                        swap.completed_date = datetime.utcnow()
                        swap.send_tx_id = res['tx_hash']
                        swap.funds_sent = True
                        db.session.add(swap)
                        db.session.commit()
                    else:
                        print('There was an error sending funds for swap {id}: {msg}'.format(
                            id=swap.id,
                            msg=res['message']
                        ))
                        if not isfile(f'/tmp/{swap.id}'):
                            post_webhook(f'Unable to send funds for swap {swap.id}: {res["message"]}')
                            with open(f'/tmp/{swap.id}', 'w') as f:
                                f.write(swap.id)
                else:
                    print('Not enough funds to transfer for swap {id}. Available: {avail}, Required: {req}'.format(
                        id=swap.id,
                        avail=crypto.from_atomic(available_funds),
                        req=crypto.from_atomic(swap.send_amount_atomic)
                    ))
                    if not isfile(f'/tmp/{swap.id}'):
                        post_webhook(f'Unable to send funds for swap {swap.id}: Not enough')
                        with open(f'/tmp/{swap.id}', 'w') as f:
                            f.write(swap.id)

        # Routes/blueprints
        from app.routes import meta, swap, api
        app.register_blueprint(meta.bp)
        app.register_blueprint(swap.bp)
        app.register_blueprint(api.bp)

        return app
