import os
import time
import traceback

from celery.schedules import crontab
from celery.task import periodic_task
from django.db.models import Q, FloatField, Sum
from django.db.models.functions import Cast
from django.utils import timezone

from accounts.models import MuleAccounts, ConsoleBotSetting, CloseWebAppTransfers
from financial.models import ExchangeRate
from futplus import settings
from futplus.celery_conf import app
from sbc.public_methods import new_print, sell_items
from accounts.web_login_utils import logout_login
from sniper.models import SniperAccount, SniperOrder, MuleDischargeMode2, DischargePerDay, DischargeMode2
from sniper.sniper_bot import SniperRunner


@app.on_after_finalize.connect
def periodic_tasks(sender, **kwargs):
    print('sniper periodic_tasks started')
    sender.add_periodic_task(10.0, sniper_manager.s(), name='active sniper manager')
    sender.add_periodic_task(120 * 60, sniper_kill_chromes.s(), name='sniper kill chromes', queue='sniper_queue')
    sender.add_periodic_task(10 * 60, manage_low_credit_mule.s(), name='manage low credit mule', queue='sniper_queue')
    sender.add_periodic_task(60 * 5, deactive_mules_discharge_mode2.s(), name='deactive mules discharge mode2')


@app.task
# @app.task(queue='sniper_queue') # todo : this code work too
def sniper_kill_chromes():
    # remove temps for seleniumwire
    # os.system("sudo pkill -f chrome")
    os.system('pgrep -f chrome | grep -v -E "can_open_chrome|sniper kill chromes|kill_chromes" | xargs sudo kill -9')
    time.sleep(5)
    os.system('rm -rf /tmp/{,.[!.],..?}*')
    time.sleep(10)
    os.system('supervisorctl restart futplus_celery_sniper_worker')
    return


@app.task
def sniper_manager():
    max_concurrency = ConsoleBotSetting.objects.get(name='celery_max_concurrency').int_value
    active_concurrency = ConsoleBotSetting.objects.get(name='celery_active_concurrency').int_value
    if active_concurrency + 10 < max_concurrency:
        receiver_accounts = SniperAccount.objects.filter(
            receiver=1, is_done=0, has_error=0, fifa_account__active=False,
            last_run_time__lte=timezone.now() - timezone.timedelta(minutes=2),
            last_run_time__gte=timezone.now() - timezone.timedelta(days=2)
        ).order_by('last_run_time')
        if not receiver_accounts:
            sniper_orders = SniperOrder.objects.filter(status=1, create_time__gt=timezone.localtime() - timezone.timedelta(days=30))
            for sniper_order in sniper_orders:
                error = 0
                done = 0
                sniper_workers = list(SniperAccount.objects.filter(sniper_order=sniper_order, receiver=1))
                for sniper_worker in sniper_workers:
                    if sniper_worker.has_error:
                        error += 1
                    if sniper_worker.is_done:
                        done += 1
                if len(sniper_workers) <= error + done and len(sniper_workers) > 0:
                    if not error:
                        sniper_order.status = 2
                    elif not done:
                        sniper_order.status = 3
                    else:
                        sniper_order.status = 4
                    sniper_order.save(update_fields=['status'])
            return 'no sniper task found'
        for receiver_acc in receiver_accounts:
            worker_order = receiver_acc.sniper_order
            sniper_senders = worker_order.sniperaccount_set.filter(
                receiver=0, has_error=0, is_done=0, fifa_account__active=False,
                last_run_time__lte=timezone.now() - timezone.timedelta(minutes=2),
                last_run_time__gte=timezone.now() - timezone.timedelta(days=2)
            )
            sniper_catcher = worker_order.sniperaccount_set.filter(
                receiver=2, has_error=0, is_done=0, fifa_account__active=False
            ).first()
            if len(sniper_senders) > 0:
                sender_acc = sniper_senders[0]
                app.send_task('sniper.tasks.active_sniper_bot', queue="sniper_queue",
                              kwargs={
                                  'sender_id': sender_acc.id, 'receiver_id': receiver_acc.id,
                                  'catcher_id': sniper_catcher.id if sniper_catcher else None,
                                  'order_id': worker_order.id,
                              })
                # active_sniper_bot.delay(sender_acc.id, receiver_acc.id, worker_order.id)
                break
            if worker_order.create_time < timezone.localtime() - timezone.timedelta(hours=2):
                receiver_acc.has_error = 1
                receiver_acc.error_description = 'worker more than 2 hours waited'
                receiver_acc.save(update_fields=['has_error', 'error_description'])
                return 'worker more than 2 hours waited'
            return 'no senders found'
    else:
        return 'more than max concurrency'


@app.task(bind=True)
def active_sniper_bot(self, sender_id, receiver_id, catcher_id, order_id):
    # if settings.SNIPER_SERVER is False:
    #     return 'I am not sniper server'
    sender = SniperAccount.objects.get(id=sender_id)
    receiver = SniperAccount.objects.get(id=receiver_id)
    if receiver.fifa_account.active is True:
        new_print(receiver.fifa_account, 'receiver is already active. can not start sniper')
        return
    if catcher_id:
        catcher = SniperAccount.objects.get(id=catcher_id)
        catcher.fifa_account.active = True
        catcher.fifa_account.save(update_fields=['active'])
    else:
        catcher = None
    new_print(receiver.fifa_account, '*' * 50, 'Sniper Started ... ', timezone.localtime(), '*' * 50)
    order = SniperOrder.objects.get(id=order_id)
    receiver.last_run_time = timezone.localtime()
    receiver.fifa_account.active = True
    receiver.task_id = self.request.id
    sender.fifa_account.active = True
    receiver.fifa_account.save(update_fields=['active'])
    sender.fifa_account.save(update_fields=['active'])
    receiver.save()
    SniperRunner(sender, receiver, catcher).transfer_coin(receiver.coins_before)
    receiver.fifa_account.refresh_from_db()
    sender.fifa_account.refresh_from_db()
    receiver.fifa_account.active = False
    sender.fifa_account.active = False
    receiver.fifa_account.save(update_fields=['active'])
    sender.fifa_account.save(update_fields=['active'])
    if catcher:
        catcher.fifa_account.active = False
        catcher.fifa_account.save(update_fields=['active'])
    new_print(receiver.fifa_account, 'Sniper Complete')


@app.task
def manage_low_credit_mule():
    from sbc.sbc_solver import SBCSolver
    counter = 0
    list_ids = []
    discharge_with_snipe_lowest_buy_now_price = ConsoleBotSetting.objects.get(
        name='discharge_with_snipe_lowest_buy_now_price').int_value
    discharge_with_mode3_lowest_target_price = ConsoleBotSetting.objects.get(
        name='discharge_with_mode3_lowest_target_price').int_value
    low_credits = MuleAccounts.objects.filter(
        Q(error_description=None) | Q(error_description=''),
        error=False,
        in_use=False, fifa_account__credit__lt=discharge_with_snipe_lowest_buy_now_price,
        deleted=False,
        fifa_account__need_captcha=False,
    )
    discharge_mode3_last_15_min = CloseWebAppTransfers.objects.filter(
        second_side_done=True,
        create_time__gte=timezone.localtime() - timezone.timedelta(minutes=11)
    ).values_list('first_account')
    low_credits2 = MuleAccounts.objects.filter(
        in_use=False,
        deleted=False,
        error=False,
        fifa_account__in=discharge_mode3_last_15_min,
        fifa_account__credit__lt=discharge_with_mode3_lowest_target_price * 3
    )
    discharge_mode3_fail_120_min = CloseWebAppTransfers.objects.filter(
        first_side_done=True,
        second_side_done=False,
        error=True,
        create_time__lte=timezone.localtime() - timezone.timedelta(minutes=40),
        create_time__gte=timezone.localtime() - timezone.timedelta(minutes=60*6)
    ).values_list('first_account')
    low_credits3 = MuleAccounts.objects.filter(
        in_use=False,
        deleted=False,
        error=False,
        last_used__lt=timezone.localtime()-timezone.timedelta(minutes=40),
        fifa_account__in=discharge_mode3_fail_120_min
    )
    low_credits4 = MuleAccounts.objects.filter(
        in_use=False,
        deleted=False,
        error=False,
        last_used__lt=timezone.localtime() - timezone.timedelta(minutes=120),
        fifa_account__credit__lt=discharge_with_mode3_lowest_target_price
    )
    low_credits5 = MuleAccounts.objects.filter(
        last_used__lt=timezone.localtime() - timezone.timedelta(minutes=180),
        deleted=False,
        error=True,
        in_use=False,
    )
    updated_mules_5 = low_credits5.update(error=False, error_description=None)
    if low_credits.count() <= 2 and low_credits2.count() < 1 and low_credits3.count() < 1 and low_credits4.count():
        return f'low credits {counter} , ids : {list_ids} , is less than 2 , snipe : {low_credits.count()}, mode3 : {low_credits2.count()}, mode3_2 : {low_credits3.count()}, mode3_3 : {low_credits4.count()} , updated mules 5 : {updated_mules_5}'
    # low_credits.update(in_use=True)
    for item in (low_credits | low_credits2 | low_credits3 | low_credits4 | low_credits5):
        item.refresh_from_db()
        if not item.in_use:
            item.in_use = True
            item.last_used = timezone.localtime()
            item.save(update_fields=['in_use', 'last_used'])
        else:
            continue
        counter += 1
        list_ids.append(item.fifa_account.id)
        try:
            last_worker = item.fifa_account.sbcworker_set.filter(running_platform__in=['web', 'inject']).last()
            last_worker.complete_number += 1
            last_worker.save(update_fields=['complete_number'])
            new_print(item.fifa_account, '****** Low credit Mules Job Start *****')
            sbc_solver_item = SBCSolver(last_worker.id, item.fifa_account.id, item.fifa_account.user_name,
                                        item.fifa_account.password,
                                        item.fifa_account.platform, last_worker.manual_loyal, use_public_moves=False)
            login_result = logout_login(sbc_solver_item, last_worker, item.fifa_account)
            # if login_result == 'login failed':
            if login_result.get('status_bool') is False:
                MuleAccounts.objects.filter(fifa_account=item.fifa_account).update(
                    error=True, error_description=login_result.get('reason'), in_use=False)
            else:
                sell_items(sbc_solver_item, last_worker, item.fifa_account)
        except:
            new_print(item.fifa_account, 'error in low credits : ', traceback.format_exc())
        # item.refresh_from_db()
        item.in_use = False
        item.save(update_fields=['in_use'])
    return f'low credits {counter} , ids : {list_ids}, updated_mules_5 : {updated_mules_5}'


@periodic_task(run_every=crontab(hour="0", minute='1'), queue='sniper_queue', options={'queue': 'sniper_queue'})
def login_to_mules():
    from sbc.sbc_solver import SBCSolver
    low_credits = MuleAccounts.objects.filter(
        error_description__in=[None, ''], in_use=False,
        deleted=False,
    )
    for item in low_credits:
        item.refresh_from_db()
        if not item.in_use:
            item.in_use = True
            item.save(update_fields=['in_use'])
        else:
            continue
        try:
            last_worker = item.fifa_account.sbcworker_set.last()
            sbc_solver_item = SBCSolver(last_worker.id, item.fifa_account.id, item.fifa_account.user_name,
                                        item.fifa_account.password,
                                        item.fifa_account.platform, last_worker.manual_loyal, use_public_moves=False)
            login_result = logout_login(sbc_solver_item, last_worker, item.fifa_account)
            if login_result.get('status_bool') is False:
                item.error = True
                item.error_description = login_result.get('reason')
                item.save(update_fields=['error', 'error_description'])
            else:
                sbc_solver_item.update_credit()
        except:
            new_print(item.fifa_account, 'error in low credits : ', traceback.format_exc())
        # item.refresh_from_db()
        item.in_use = False
        item.save(update_fields=['in_use'])
    return f'low credits {low_credits.count()}'


@app.task
def deactive_mules_discharge_mode2():
    deactivated_mules = MuleDischargeMode2.objects.filter(
        in_use=True, last_run_time__lt=timezone.localtime() - timezone.timedelta(minutes=15)
    ).update(in_use=False)
    DischargeMode2.objects.filter(
        create_time__lt=timezone.localtime() - timezone.timedelta(minutes=45),
        status=None
    ).update(status='not_bought', error_description='too many waiting')
    return deactivated_mules


@periodic_task(run_every=crontab(hour='23', minute='50'))
def save_discharge_per_day():
    local_now = timezone.localtime()

    discharge_24_hour = DischargeMode2.objects.filter(
        status__in=['success', 'failed'],
        create_time__lte=local_now, create_time__gte=local_now - timezone.timedelta(hours=24),
    ).annotate(
        income_credit_float=Cast('income_credit', output_field=FloatField())
    ).values('fifa_account').annotate(
        sum_success_discharge=Sum('income_credit_float')
    ).values_list('fifa_account__id', 'sum_success_discharge')
    discharge_bulk = []
    last_euro_usdt = ExchangeRate.objects.filter(name='euro-usdt').last()
    if last_euro_usdt:
        last_euro_usdt = float(last_euro_usdt.latest)
    else:
        last_euro_usdt = 0
    for item in discharge_24_hour:
        discharge_bulk.append(DischargePerDay(
            create_time=local_now,
            fifa_account_id=item[0],
            income_credit=item[1],
            usdt_amount=item[1] * last_euro_usdt,
            euro_amount=item[1]
        ))
    DischargePerDay.objects.bulk_create(discharge_bulk)