import json
import logging
import traceback
import itertools
from typing import List
import base64
from datetime import datetime, timedelta

from django.db import transaction
from django.db.models import Q
from django.utils.module_loading import import_string
from django_q.models import Schedule
from django_q.tasks import Chain, async_task

from netsuitesdk.internal.exceptions import NetSuiteRequestError
from netsuitesdk import NetSuiteRateLimitError, NetSuiteLoginError

from fyle_accounting_mappings.models import ExpenseAttribute, Mapping, DestinationAttribute, CategoryMapping, EmployeeMapping
from fyle_integrations_platform_connector import PlatformConnector
from fyle.platform.exceptions import InternalServerError, InvalidTokenError

from fyle_netsuite_api.exceptions import BulkError

from apps.fyle.models import ExpenseGroup, Expense, Reimbursement
from apps.mappings.models import GeneralMapping, SubsidiaryMapping
from apps.tasks.models import TaskLog
from apps.workspaces.models import NetSuiteCredentials, FyleCredential, Configuration, Workspace

from .models import Bill, BillLineitem, ExpenseReport, ExpenseReportLineItem, JournalEntry, JournalEntryLineItem, \
    VendorPayment, VendorPaymentLineitem, CreditCardCharge, CreditCardChargeLineItem
from .connector import NetSuiteConnector

logger = logging.getLogger(__name__)
logger.level = logging.INFO

netsuite_paid_state = 'Paid In Full'
netsuite_error_message = 'NetSuite System Error'

TASK_TYPE_CONSTRUCT_LINE_FUNC_MAP = {
    'CREATING_EXPENSE_REPORT': 'construct_expense_report_lineitems',
    'CREATING_BILL': 'construct_bill_lineitems',
    'CREATING_JOURNAL_ENTRY': 'construct_journal_entry_lineitems',
    'CREATING_CREDIT_CARD_CHARGE': 'construct_credit_card_charge_lineitems'
}

TASK_TYPE_EXPORT_COL_MAP = {
    'CREATING_EXPENSE_REPORT': 'expense_report',
    'CREATING_BILL': 'bill',
    'CREATING_JOURNAL_ENTRY': 'journal_entry',
    'CREATING_CREDIT_CARD_CHARGE': 'credit_card_charge'
}

TASK_TYPE_LINE_ITEM_COL_MAP = {
    'CREATING_EXPENSE_REPORT': 'ExpenseReportLineItem',
    'CREATING_BILL': 'BillLineitem',
    'CREATING_JOURNAL_ENTRY': 'JournalEntryLineItem',
    'CREATING_CREDIT_CARD_CHARGE': 'CreditCardChargeLineItem'
}

TASK_TYPE_EXPORT_MAP = {
    'CREATING_EXPENSE_REPORT': 'expense_reports',
    'CREATING_BILL': 'vendor_bills',
    'CREATING_JOURNAL_ENTRY': 'journal_entries'
}

TASK_TYPE_LINES_MAP = {
    'CREATING_EXPENSE_REPORT': 'expenseList',
    'CREATING_BILL': 'expenseList',
    'CREATING_JOURNAL_ENTRY': 'lineList',
    'CREATING_CREDIT_CARD_CHARGE': 'expenses',
}

def load_attachments(netsuite_connection: NetSuiteConnector, expense: Expense, expense_group: ExpenseGroup):
    """
    Get attachments from Fyle
    :param netsuite_connection: NetSuite Connection
    :param expense_id: Fyle expense id
    :param expense_group: Integration Expense group
    """
    workspace_id = expense_group.workspace_id
    workspace = expense_group.workspace

    try:
        fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
        file_ids = expense.file_ids
        platform = PlatformConnector(fyle_credentials)

        files_list = []
        attachments = []
        receipt_url = None

        if file_ids and len(file_ids):
            folder = netsuite_connection.connection.folders.post({
                "externalId": workspace.fyle_org_id,
                "name": 'Fyle Attachments - {0}'.format(workspace.name)
            })

            for file_id in file_ids:
                files_list.append({'id': file_id})

            attachments = platform.files.bulk_generate_file_urls(files_list)

            # Filter HTML attachments
            attachments = list(filter(lambda attachment: attachment['content_type'] != 'text/html', attachments))

            if attachments:
                for attachment in attachments:
                    netsuite_connection.connection.files.post({
                        "externalId": expense.expense_id,
                        "name": '{0}_{1}'.format(attachment['id'], attachment['name']),
                        'content': base64.b64decode(attachment['download_url']),
                        "folder": {
                            "name": None,
                            "internalId": folder['internalId'],
                            "externalId": folder['externalId'],
                            "type": "folder"
                        }
                    })
                    break

                file = netsuite_connection.connection.files.get(externalId=expense.expense_id)
                receipt_url = file['url']

        return receipt_url

    except InvalidTokenError:
        logger.info('Invalid Fyle refresh token for workspace %s', workspace_id)

    except Exception:
        error = traceback.format_exc()
        logger.info(
            'Attachment failed for expense group id %s / workspace id %s Error: %s',
            expense.expense_id, workspace_id, {'error': error}
        )

def get_or_create_credit_card_vendor(expense_group: ExpenseGroup, merchant: str, auto_create_merchants: bool):
    """
    Get or create car default vendor
    :param expense_group: Expense Group
    :param merchant: Fyle Expense Merchant
    :param auto_create_merchants: Create merchant if doesn't exist
    :return:
    """
    netsuite_credentials = NetSuiteCredentials.objects.get(workspace_id=expense_group.workspace_id)
    netsuite_connection = NetSuiteConnector(
        netsuite_credentials=netsuite_credentials, workspace_id=int(expense_group.workspace_id))

    vendor = netsuite_connection.connection.vendors.search(attribute='entityId', value=merchant, operator='is')

    if not vendor:
        if auto_create_merchants and merchant is not None:
            created_vendor = netsuite_connection.post_vendor(expense_group=expense_group, merchant=merchant)
            return netsuite_connection.create_destination_attribute('vendor', merchant, created_vendor['internalId'])
    else:
        vendor = vendor[0]
        return netsuite_connection.create_destination_attribute(
            'vendor', vendor['entityId'], vendor['internalId'])

def __log_error(task_log: TaskLog) -> None:
    logger.exception('Something unexpected happened workspace_id: %s %s', task_log.workspace_id, task_log.detail)

def create_or_update_employee_mapping(expense_group: ExpenseGroup, netsuite_connection: NetSuiteConnector,
                                      auto_map_employees_preference: str, employee_field_mapping: str):
    try:
        employee = get_employee_expense_attribute(expense_group.description.get('employee_email'), expense_group.workspace_id)
        if not employee:
            # Sync inactive employee and gracefully handle export failure
            employee = sync_inactive_employee(expense_group)

        mapping = EmployeeMapping.objects.get(
            source_employee=employee,
            workspace_id=expense_group.workspace_id
        )

        mapping = mapping.destination_employee if employee_field_mapping == 'EMPLOYEE' \
            else mapping.destination_vendor

        if not mapping:
            raise EmployeeMapping.DoesNotExist
    except EmployeeMapping.DoesNotExist:
        source_employee = ExpenseAttribute.objects.get(
            workspace_id=expense_group.workspace_id,
            attribute_type='EMPLOYEE',
            value=expense_group.description.get('employee_email')
        )

        try:
            filters = {}
            if auto_map_employees_preference == 'EMAIL':
                filters = {
                    'detail__email__iexact': source_employee.value,
                    'attribute_type': employee_field_mapping
                }
            elif auto_map_employees_preference == 'NAME':
                filters = {
                    'value__iexact': source_employee.detail['full_name'],
                    'attribute_type': employee_field_mapping
                }

            created_entity = DestinationAttribute.objects.filter(
                workspace_id=expense_group.workspace_id,
                **filters
            ).first()

            existing_employee_mapping = EmployeeMapping.objects.filter(
                source_employee=source_employee
            ).first()

            destination = {}
            if employee_field_mapping == 'EMPLOYEE':
                if created_entity is None:
                    created_entity: DestinationAttribute = netsuite_connection.get_or_create_employee(
                        source_employee, expense_group)
                    destination['destination_employee_id'] = created_entity.id
                elif existing_employee_mapping and existing_employee_mapping.destination_employee:
                    destination['destination_employee_id'] = existing_employee_mapping.destination_employee.id
            else:
                if created_entity is None:
                    created_entity: DestinationAttribute = netsuite_connection.get_or_create_vendor(
                        source_employee, expense_group)
                    destination['destination_vendor_id'] = created_entity.id
                elif existing_employee_mapping and existing_employee_mapping.destination_vendor:
                    destination['destination_vendor_id'] = existing_employee_mapping.destination_vendor.id

            if existing_employee_mapping and existing_employee_mapping.destination_card_account:
                destination['destination_card_account_id'] = existing_employee_mapping.destination_card_account.id

            if 'destination_employee_id' not in destination or not destination['destination_employee_id']:
                destination['destination_employee_id'] = created_entity.id

            if 'destination_vendor_id' not in destination or not destination['destination_vendor_id']:
                destination['destination_vendor_id'] = created_entity.id

            mapping = EmployeeMapping.create_or_update_employee_mapping(
                source_employee_id=source_employee.id,
                workspace=expense_group.workspace,
                **destination
            )

            mapping.source_employee.auto_mapped = True
            mapping.source_employee.save()

            if employee_field_mapping == 'EMPLOYEE':
                mapping.destination_employee.auto_created = True
                mapping.destination_employee.save()
            elif employee_field_mapping == 'VENDOR':
                mapping.destination_vendor.auto_created = True
                mapping.destination_vendor.save()

        except NetSuiteRequestError as exception:
            logger.info({'error': exception})

def __handle_netsuite_connection_error(expense_group: ExpenseGroup, task_log: TaskLog) -> None:
    logger.info(
        'NetSuite Credentials not found for workspace_id %s / expense group %s',
        expense_group.id,
        expense_group.workspace_id
    )
    detail = {
        'expense_group_id': expense_group.id,
        'message': 'NetSuite Account not connected'
    }
    task_log.status = 'FAILED'
    task_log.detail = detail

    task_log.save()

def construct_payload_and_update_export(expense_id_receipt_url_map: dict, task_log: TaskLog, workspace: Workspace,
    cluster_domain: str, netsuite_connection: NetSuiteConnector):
    """
    Construct payload and update export
    :param expense_id_receipt_url_map: expense_id_receipt_url_map ex - {'tx4ziVSAyIsv': 'receipt_url_1', 'tx4ziVSAyIs2': 'receipt_url_2'}
    :param task_log: task_log
    :param workspace: workspace
    :param cluster_domain: cluster_domain
    :param netsuite_connection: netsuite_connection
    :return: None
    """
    if expense_id_receipt_url_map:
        # target function that constructs lines payload, ex - construct_expense_report_lineitems
        func = TASK_TYPE_CONSTRUCT_LINE_FUNC_MAP[task_log.type]

        # this holds the export instance, ex - ExpenseReport / JournalEntry / Bill
        export_instance = getattr(task_log, TASK_TYPE_EXPORT_COL_MAP[task_log.type])

        # this holds the line items filter for a given export instance, ex - {'expense_report_id': 1}
        line_items_filter = {
            '{}_id'.format(TASK_TYPE_EXPORT_COL_MAP[task_log.type]): export_instance.id
        }

        # this holds the line item model, ex - ExpenseReportLineitem / JournalEntryLineitem / BillLineitem
        line_item_model = import_string('apps.netsuite.models.{}'.format(TASK_TYPE_LINE_ITEM_COL_MAP[task_log.type]))
        export_line_items = line_item_model.objects.filter(**line_items_filter)

        lines = []
        expense_list = []
        item_list = []
        payload = {}

        # Since we have credit and debit for Journal Entry lines, we need to construct them separately
        if task_log.type == 'CREATING_JOURNAL_ENTRY':
            construct_lines = getattr(netsuite_connection, func)

            # calling the target construct payload function with credit and debit
            credit_line = construct_lines(export_line_items, credit='Credit', org_id=workspace.fyle_org_id)
            debit_line = construct_lines(
                export_line_items, debit='Debit', attachment_links=expense_id_receipt_url_map,
                cluster_domain=cluster_domain, org_id=workspace.fyle_org_id
            )
            lines.extend(credit_line)
            lines.extend(debit_line)

        elif task_log.type == 'CREATING_BILL':
            construct_lines = getattr(netsuite_connection, func)
            # calling the target construct payload function
            expense_list, item_list = construct_lines(export_line_items, expense_id_receipt_url_map, cluster_domain, workspace.fyle_org_id)
        else:
            construct_lines = getattr(netsuite_connection, func)
            # calling the target construct payload function
            lines = construct_lines(export_line_items, expense_id_receipt_url_map, cluster_domain, workspace.fyle_org_id)

        # final payload to be sent to netsuite, since this is an update operation, we need to pass the external id
        if task_log.type == 'CREATING_BILL':
            payload = {
                TASK_TYPE_LINES_MAP[task_log.type]: expense_list,
                'itemList': item_list,
                'externalId': export_instance.external_id
            }
        else:
            payload = {
                TASK_TYPE_LINES_MAP[task_log.type]: lines,
                'externalId': export_instance.external_id
            }

        # calling the target netsuite post function, ex - netsuite_connection.connection.expense_reports.post(payload)
        getattr(netsuite_connection.connection, TASK_TYPE_EXPORT_MAP[task_log.type]).post(payload)

        # Update netsuite_receipt_url in line_items table
        for line_item in export_line_items:
            line_item.netsuite_receipt_url = expense_id_receipt_url_map.get(line_item.expense.expense_id, None)
            line_item.save()

def upload_attachments_and_update_export(expenses: List[Expense], task_log: TaskLog, fyle_credentials: FyleCredential, workspace_id: int):
    """
    Upload attachments and update export
    :param expenses: list of expenses
    :param task_log: task_log
    :param fyle_credentials: fyle_credentials
    :param workspace_id: workspace_id
    :return: None
    """
    try:
        netsuite_credentials = NetSuiteCredentials.objects.get(workspace_id=workspace_id)
        netsuite_connection = NetSuiteConnector(netsuite_credentials, workspace_id)
        workspace = netsuite_credentials.workspace

        platform = PlatformConnector(fyle_credentials=fyle_credentials)

        expense_id_receipt_url_map = {}

        for expense in expenses:
            if expense.file_ids and len(expense.file_ids):
                # Grabbing 1st attachment since we can upload only 1 attachment per expense
                payload = [{'id': expense.file_ids[0]}]
                attachments = platform.files.bulk_generate_file_urls(payload)

                attachment = attachments[0] if len(attachments) else None

                if attachment:
                    netsuite_connection.connection.files.post({
                        'externalId': expense.expense_id,
                        'name': '{0}_{1}'.format(attachment['id'], attachment['name']),
                        'content': base64.b64decode(attachment['download_url']),
                        'folder': {
                            'name': None,
                            'internalId': None,
                            'externalId': workspace.fyle_org_id,
                            'type': 'folder'
                        }
                    })

                    file = netsuite_connection.connection.files.get(externalId=expense.expense_id)
                    receipt_url = file['url']
                    expense_id_receipt_url_map[expense.expense_id] = receipt_url

        construct_payload_and_update_export(expense_id_receipt_url_map, task_log, workspace, fyle_credentials.cluster_domain, netsuite_connection)

    except (NetSuiteRateLimitError, NetSuiteRequestError, NetSuiteLoginError, InvalidTokenError) as exception:
        logger.info('Error while uploading attachments to netsuite workspace_id - %s %s', workspace_id, exception.__dict__)

    except Exception as exception:
        logger.error(
            'Error while uploading attachments to netsuite workspace_id - %s %s %s',
            workspace_id, exception, traceback.format_exc()
        )

def create_bill(expense_group, task_log_id):
    task_log = TaskLog.objects.get(id=task_log_id)

    if task_log.status not in ['IN_PROGRESS', 'COMPLETE']:
        task_log.status = 'IN_PROGRESS'
        task_log.save()
    else:
        return

    configuration: Configuration = Configuration.objects.get(workspace_id=expense_group.workspace_id)
    general_mappings: GeneralMapping = GeneralMapping.objects.filter(workspace_id=expense_group.workspace_id).first()

    try:
        fyle_credentials = FyleCredential.objects.get(workspace_id=expense_group.workspace_id)
        netsuite_credentials = NetSuiteCredentials.objects.get(workspace_id=expense_group.workspace_id)

        netsuite_connection = NetSuiteConnector(netsuite_credentials, expense_group.workspace_id)

        if expense_group.fund_source == 'PERSONAL' and configuration.auto_map_employees \
                and configuration.auto_create_destination_entity:
            create_or_update_employee_mapping(
                expense_group, netsuite_connection, configuration.auto_map_employees,
                configuration.employee_field_mapping)

        if general_mappings and general_mappings.use_employee_department and expense_group.fund_source == 'CCC' \
                and configuration.auto_map_employees and configuration.auto_create_destination_entity:
            create_or_update_employee_mapping(
                expense_group, netsuite_connection, configuration.auto_map_employees,
                configuration.employee_field_mapping)

        __validate_expense_group(expense_group, configuration)
        with transaction.atomic():
            bill_object = Bill.create_bill(expense_group)

            bill_lineitems_objects = BillLineitem.create_bill_lineitems(expense_group, configuration)

            created_bill = netsuite_connection.post_bill(bill_object, bill_lineitems_objects)

            task_log.detail = created_bill
            task_log.bill = bill_object
            task_log.status = 'COMPLETE'

            task_log.save()

            expense_group.exported_at = datetime.now()
            expense_group.response_logs = created_bill
            expense_group.save()

            async_task(
                'apps.netsuite.tasks.upload_attachments_and_update_export',
                expense_group.expenses.all(), task_log, fyle_credentials, expense_group.workspace_id
            )

    except NetSuiteCredentials.DoesNotExist:
        __handle_netsuite_connection_error(expense_group, task_log)

    except (NetSuiteRequestError, NetSuiteLoginError) as exception:
        all_details = []
        logger.info({'error': exception})
        detail = json.dumps(exception.__dict__)
        detail = json.loads(detail)
        task_log.status = 'FAILED'

        all_details.append({
            'expense_group_id': expense_group.id,
            'value': netsuite_error_message,
            'type': detail['code'],
            'message': detail['message']
        })
        task_log.detail = all_details

        task_log.save()

    except BulkError as exception:
        logger.info(exception.response)
        detail = exception.response
        task_log.status = 'FAILED'
        task_log.detail = detail

        task_log.save()

    except NetSuiteRateLimitError:
        logger.info('Rate limit error, workspace_id - %s', expense_group.workspace_id)
        task_log.status = 'FAILED'
        task_log.detail = {
            'error': 'Rate limit error'
        }

        task_log.save()

    except Exception:
        error = traceback.format_exc()
        task_log.detail = {
            'error': error
        }
        task_log.status = 'FATAL'
        task_log.save()
        __log_error(task_log)

def create_credit_card_charge(expense_group, task_log_id):
    task_log = TaskLog.objects.get(id=task_log_id)

    if task_log.status not in ['IN_PROGRESS', 'COMPLETE']:
        task_log.status = 'IN_PROGRESS'
        task_log.save()
    else:
        return

    configuration = Configuration.objects.get(workspace_id=expense_group.workspace_id)
    general_mappings: GeneralMapping = GeneralMapping.objects.filter(workspace_id=expense_group.workspace_id).first()

    try:
        netsuite_credentials = NetSuiteCredentials.objects.get(workspace_id=expense_group.workspace_id)

        netsuite_connection = NetSuiteConnector(netsuite_credentials, expense_group.workspace_id)

        if general_mappings and general_mappings.use_employee_department and expense_group.fund_source == 'CCC' \
                and configuration.auto_map_employees and configuration.auto_create_destination_entity:
            create_or_update_employee_mapping(
                expense_group, netsuite_connection, configuration.auto_map_employees,
                configuration.employee_field_mapping)

        merchant = expense_group.expenses.first().vendor
        auto_create_merchants = configuration.auto_create_merchants
        get_or_create_credit_card_vendor(expense_group, merchant, auto_create_merchants)

        __validate_expense_group(expense_group, configuration)
        with transaction.atomic():
            credit_card_charge_object = CreditCardCharge.create_credit_card_charge(expense_group)

            credit_card_charge_lineitems_object = CreditCardChargeLineItem.create_credit_card_charge_lineitem(
                expense_group, configuration
            )
            attachment_links = {}

            expense = expense_group.expenses.first()
            refund = False
            if expense.amount < 0:
                refund = True

            attachment_link = load_attachments(netsuite_connection, expense, expense_group)

            if attachment_link:
                attachment_links[expense.expense_id] = attachment_link

            created_credit_card_charge = netsuite_connection.post_credit_card_charge(
                credit_card_charge_object, credit_card_charge_lineitems_object, attachment_links, refund
            )

            if refund:
                created_credit_card_charge['type'] = 'chargeCardRefund'
            else:
                created_credit_card_charge['type'] = 'chargeCard'

            task_log.detail = created_credit_card_charge
            task_log.credit_card_charge = credit_card_charge_object
            task_log.status = 'COMPLETE'

            task_log.save()

            expense_group.exported_at = datetime.now()
            expense_group.response_logs = created_credit_card_charge
            expense_group.save()

    except NetSuiteCredentials.DoesNotExist:
        __handle_netsuite_connection_error(expense_group, task_log)

    except (NetSuiteRequestError, NetSuiteLoginError) as exception:
        all_details = []
        logger.info({'error': exception})
        detail = json.dumps(exception.__dict__)
        detail = json.loads(detail)
        task_log.status = 'FAILED'

        all_details.append({
            'expense_group_id': expense_group.id,
            'value': netsuite_error_message,
            'type': detail['code'],
            'message': detail['message']
        })
        task_log.detail = all_details

        task_log.save()

    except BulkError as exception:
        logger.info(exception.response)
        detail = exception.response
        task_log.status = 'FAILED'
        task_log.detail = detail

        task_log.save()

    except NetSuiteRateLimitError:
        logger.info('Rate limit error, workspace_id - %s', expense_group.workspace_id)
        task_log.status = 'FAILED'
        task_log.detail = {
            'error': 'Rate limit error'
        }

        task_log.save()

    except Exception:
        error = traceback.format_exc()
        task_log.detail = {
            'error': error
        }
        task_log.status = 'FATAL'
        task_log.save()
        __log_error(task_log)

def create_expense_report(expense_group, task_log_id):
    task_log = TaskLog.objects.get(id=task_log_id)

    if task_log.status not in ['IN_PROGRESS', 'COMPLETE']:
        task_log.status = 'IN_PROGRESS'
        task_log.save()
    else:
        return

    configuration = Configuration.objects.get(workspace_id=expense_group.workspace_id)

    try:
        fyle_credentials = FyleCredential.objects.get(workspace_id=expense_group.workspace_id)
        netsuite_credentials = NetSuiteCredentials.objects.get(workspace_id=expense_group.workspace_id)
        netsuite_connection = NetSuiteConnector(netsuite_credentials, expense_group.workspace_id)

        if configuration.auto_map_employees and configuration.auto_create_destination_entity:
            create_or_update_employee_mapping(
                expense_group, netsuite_connection, configuration.auto_map_employees,
                configuration.employee_field_mapping)

        __validate_expense_group(expense_group, configuration)
        with transaction.atomic():
            expense_report_object = ExpenseReport.create_expense_report(expense_group)

            expense_report_lineitems_objects = ExpenseReportLineItem.create_expense_report_lineitems(
                expense_group, configuration
            )

            created_expense_report = netsuite_connection.post_expense_report(
                expense_report_object, expense_report_lineitems_objects
            )

            task_log.detail = created_expense_report
            task_log.expense_report = expense_report_object
            task_log.status = 'COMPLETE'

            task_log.save()

            expense_group.exported_at = datetime.now()
            expense_group.response_logs = created_expense_report
            expense_group.save()

            async_task(
                'apps.netsuite.tasks.upload_attachments_and_update_export',
                expense_group.expenses.all(), task_log, fyle_credentials, expense_group.workspace_id
            )

    except NetSuiteCredentials.DoesNotExist:
        __handle_netsuite_connection_error(expense_group, task_log)

    except (NetSuiteRequestError, NetSuiteLoginError) as exception:
        all_details = []
        logger.info({'error': exception})
        detail = json.dumps(exception.__dict__)
        detail = json.loads(detail)
        task_log.status = 'FAILED'

        all_details.append({
            'expense_group_id': expense_group.id,
            'value': netsuite_error_message,
            'type': detail['code'],
            'message': detail['message']
        })
        task_log.detail = all_details

        task_log.save()

    except BulkError as exception:
        logger.info(exception.response)
        detail = exception.response
        task_log.status = 'FAILED'
        task_log.detail = detail

        task_log.save()

    except NetSuiteRateLimitError:
        logger.info('Rate limit error, workspace_id - %s', expense_group.workspace_id)
        task_log.status = 'FAILED'
        task_log.detail = {
            'error': 'Rate limit error'
        }

        task_log.save()

    except Exception:
        error = traceback.format_exc()
        task_log.detail = {
            'error': error
        }
        task_log.status = 'FATAL'
        task_log.save()
        __log_error(task_log)

def create_journal_entry(expense_group, task_log_id):
    task_log = TaskLog.objects.get(id=task_log_id)

    if task_log.status not in ['IN_PROGRESS', 'COMPLETE']:
        task_log.status = 'IN_PROGRESS'
        task_log.save()
    else:
        return

    configuration = Configuration.objects.get(workspace_id=expense_group.workspace_id)

    try:
        fyle_credentials = FyleCredential.objects.get(workspace_id=expense_group.workspace_id)
        netsuite_credentials = NetSuiteCredentials.objects.get(workspace_id=expense_group.workspace_id)

        netsuite_connection = NetSuiteConnector(netsuite_credentials, expense_group.workspace_id)

        if configuration.auto_map_employees and configuration.auto_create_destination_entity:
            create_or_update_employee_mapping(
                expense_group, netsuite_connection, configuration.auto_map_employees,
                configuration.employee_field_mapping)
        __validate_expense_group(expense_group, configuration)
        with transaction.atomic():
            journal_entry_object = JournalEntry.create_journal_entry(expense_group)

            journal_entry_lineitems_objects = JournalEntryLineItem.create_journal_entry_lineitems(
                expense_group, configuration
            )

            created_journal_entry = netsuite_connection.post_journal_entry(
                journal_entry_object, journal_entry_lineitems_objects
            )

            task_log.detail = created_journal_entry
            task_log.journal_entry = journal_entry_object
            task_log.status = 'COMPLETE'

            task_log.save()

            expense_group.exported_at = datetime.now()
            expense_group.response_logs = created_journal_entry
            expense_group.save()

            async_task(
                'apps.netsuite.tasks.upload_attachments_and_update_export',
                expense_group.expenses.all(), task_log, fyle_credentials, expense_group.workspace_id
            )

    except NetSuiteCredentials.DoesNotExist:
        __handle_netsuite_connection_error(expense_group, task_log)

    except (NetSuiteRequestError, NetSuiteLoginError) as exception:
        all_details = []
        logger.info({'error': exception})
        detail = json.dumps(exception.__dict__)
        detail = json.loads(detail)
        task_log.status = 'FAILED'

        all_details.append({
            'expense_group_id': expense_group.id,
            'value': netsuite_error_message,
            'type': detail['code'],
            'message': detail['message']
        })
        task_log.detail = all_details

        task_log.save()

    except BulkError as exception:
        logger.info(exception.response)
        detail = exception.response
        task_log.status = 'FAILED'
        task_log.detail = detail

        task_log.save()

    except NetSuiteRateLimitError:
        logger.info('Rate limit error, workspace_id - %s', expense_group.workspace_id)
        task_log.status = 'FAILED'
        task_log.detail = {
            'error': 'Rate limit error'
        }

        task_log.save()

    except Exception:
        error = traceback.format_exc()
        task_log.detail = {
            'error': error
        }
        task_log.status = 'FATAL'
        task_log.save()
        __log_error(task_log)

def __validate_general_mapping(expense_group: ExpenseGroup, configuration: Configuration) -> List[BulkError]:
    bulk_errors = []
    general_mapping = None
    error_type = 'General Mappings'

    try:
        general_mapping = GeneralMapping.objects.get(workspace_id=expense_group.workspace_id)
    except GeneralMapping.DoesNotExist:
        bulk_errors.append({
            'row': None,
            'expense_group_id': expense_group.id,
            'value': error_type,
            'type': error_type,
            'message': '{} not found'.format(error_type)
        })

    if general_mapping:
        if not (general_mapping.accounts_payable_id or general_mapping.accounts_payable_name) \
            and (
                    (
                    configuration.reimbursable_expenses_object == 'BILL' or \
                        configuration.corporate_credit_card_expenses_object == 'BILL'
                    ) or (
                        configuration.reimbursable_expenses_object == 'JOURNAL ENTRY' and \
                            configuration.employee_field_mapping == 'VENDOR' and \
                                expense_group.fund_source == 'PERSONAL'
                    )
                ):
            bulk_errors.append({
                'row': None,
                'expense_group_id': expense_group.id,
                'value': 'Accounts Payable',
                'type': error_type,
                'message': 'Accounts Payable not found'
            })

        if not (general_mapping.reimbursable_account_id or general_mapping.reimbursable_account_name) \
            and (
                    (
                        configuration.reimbursable_expenses_object == 'EXPENSE REPORT'
                    ) or (
                        configuration.reimbursable_expenses_object == 'JOURNAL ENTRY' and \
                                configuration.employee_field_mapping == 'EMPLOYEE' and \
                                    expense_group.fund_source == 'PERSONAL'
                    )
            ):
            bulk_errors.append({
                'row': None,
                'expense_group_id': expense_group.id,
                'value': 'Reimbursable Account',
                'type': error_type,
                'message': 'Reimbursable Account not found'
            })

        if not (general_mapping.default_ccc_account_id or general_mapping.default_ccc_account_name) and \
            expense_group.fund_source == 'CCC' and \
                configuration.corporate_credit_card_expenses_object in ['CREDIT CARD CHARGE', 'JOURNAL ENTRY']:
            bulk_errors.append({
                'row': None,
                'expense_group_id': expense_group.id,
                'value': 'Default Credit Card Account',
                'type': error_type,
                'message': 'Default Credit Card Account not found'
            })

        if not (general_mapping.default_ccc_vendor_id or general_mapping.default_ccc_vendor_name) and \
            configuration.corporate_credit_card_expenses_object in ['BILL', 'CREDIT CARD CHARGE'] and \
                expense_group.fund_source == 'CCC':
            bulk_errors.append({
                'row': None,
                'expense_group_id': expense_group.id,
                'value': expense_group.description.get('employee_email'),
                'type': error_type,
                'message': 'Default Credit Card Vendor not found'
            })

        if not (general_mapping.default_ccc_vendor_id or general_mapping.default_ccc_vendor_name) and \
            expense_group.fund_source == 'CCC' and \
                configuration.corporate_credit_card_expenses_object == 'JOURNAL ENTRY' and configuration.name_in_journal_entry == 'MERCHANT' :
            bulk_errors.append({
                'row': None,
                'expense_group_id': expense_group.id,
                'value': 'Default Journal Entry Vendor',
                'type': error_type,
                'message': 'Default Journal Entry Vendor not found'
            })

    return bulk_errors

def __validate_subsidiary_mapping(expense_group: ExpenseGroup) -> List[BulkError]:
    bulk_errors = []
    try:
        SubsidiaryMapping.objects.get(workspace_id=expense_group.workspace_id)
    except SubsidiaryMapping.DoesNotExist:
        bulk_errors.append({
            'row': None,
            'expense_group_id': expense_group.id,
            'value': 'subsidiary mappings',
            'type': 'Subsidiary Mappings',
            'message': 'Subsidiary mapping not found'
        })

    return bulk_errors

def __validate_tax_group_mapping(expense_group: ExpenseGroup, configuration: Configuration) -> List[BulkError]:
    row = 0
    bulk_errors = []
    expenses = expense_group.expenses.all()

    for lineitem in expenses:
        if configuration.import_tax_items and lineitem.tax_group_id:
            tax_group  = ExpenseAttribute.objects.get(
                workspace_id=expense_group.workspace_id,
                attribute_type='TAX_GROUP',
                source_id=lineitem.tax_group_id
            )

            tax_code = Mapping.objects.filter(
                source_type='TAX_GROUP',
                source__value=tax_group.value,
                workspace_id=expense_group.workspace_id
            ).first()

            if not tax_code:
                bulk_errors.append({
                    'row': row,
                    'expense_group_id': expense_group.id,
                    'value': tax_group.value,
                    'type': 'Tax Group Mapping',
                    'message': 'Tax Group Mapping not found'
                })

        row = row + 1

    return bulk_errors

def get_employee_expense_attribute(value: str, workspace_id: int) -> ExpenseAttribute:
    """
    Get employee expense attribute
    :param value: value
    :param workspace_id: workspace id
    """
    return ExpenseAttribute.objects.filter(
        attribute_type='EMPLOYEE',
        value=value,
        workspace_id=workspace_id
    ).first()

def sync_inactive_employee(expense_group: ExpenseGroup) -> ExpenseAttribute:
    fyle_credentials = FyleCredential.objects.get(workspace_id=expense_group.workspace_id)
    platform = PlatformConnector(fyle_credentials=fyle_credentials)
    fyle_employee = platform.employees.get_employee_by_email(expense_group.description.get('employee_email'))
    if len(fyle_employee):
        fyle_employee = fyle_employee[0]
        attribute = {
            'attribute_type': 'EMPLOYEE',
            'display_name': 'Employee',
            'value': fyle_employee['user']['email'],
            'source_id': fyle_employee['id'],
            'active': True if fyle_employee['is_enabled'] and fyle_employee['has_accepted_invite'] else False,
            'detail': {
                'user_id': fyle_employee['user_id'],
                'employee_code': fyle_employee['code'],
                'full_name': fyle_employee['user']['full_name'],
                'location': fyle_employee['location'],
                'department': fyle_employee['department']['name'] if fyle_employee['department'] else None,
                'department_id': fyle_employee['department_id'],
                'department_code': fyle_employee['department']['code'] if fyle_employee['department'] else None
            }
        }
        ExpenseAttribute.bulk_create_or_update_expense_attributes([attribute], 'EMPLOYEE', expense_group.workspace_id, True)
        return get_employee_expense_attribute(expense_group.description.get('employee_email'), expense_group.workspace_id)

    return None

def __validate_employee_mapping(expense_group: ExpenseGroup, configuration: Configuration) -> List[BulkError]:
    employee = get_employee_expense_attribute(expense_group.description.get('employee_email'), expense_group.workspace_id)

    if not employee:
        # Sync inactive employee and gracefully handle export failure
        employee = sync_inactive_employee(expense_group)

    bulk_errors = []
    if expense_group.fund_source == 'PERSONAL' or configuration.name_in_journal_entry == 'EMPLOYEE' or \
            (expense_group.fund_source == 'CCC' and configuration.corporate_credit_card_expenses_object == 'EXPENSE REPORT'):
        try:
            entity = EmployeeMapping.objects.get(
                source_employee=employee,
                workspace_id=expense_group.workspace_id
            )

            if configuration.employee_field_mapping == 'EMPLOYEE':
                entity = entity.destination_employee
            else:
                entity = entity.destination_vendor

            if not entity:
                raise EmployeeMapping.DoesNotExist
        except EmployeeMapping.DoesNotExist:
            bulk_errors.append({
                'row': None,
                'expense_group_id': expense_group.id,
                'value': expense_group.description.get('employee_email'),
                'type': 'Employee Mapping',
                'message': 'Employee mapping not found'
            })

    return bulk_errors

def __validate_category_mapping(expense_group: ExpenseGroup, configuration: Configuration) -> List[BulkError]:
    row = 0
    bulk_errors = []
    expenses = expense_group.expenses.all()

    for lineitem in expenses:
        category = lineitem.category if (lineitem.category == lineitem.sub_category or lineitem.sub_category == None) else '{0} / {1}'.format(
            lineitem.category, lineitem.sub_category)

        category_mapping = CategoryMapping.objects.filter(
            source_category__value=category,
            workspace_id=expense_group.workspace_id
        ).first()

        if category_mapping:
            if expense_group.fund_source == 'PERSONAL':
                if configuration.reimbursable_expenses_object == 'EXPENSE REPORT':
                    category_mapping = category_mapping.destination_expense_head
                else:
                    category_mapping = category_mapping.destination_account
            else:
                if configuration.corporate_credit_card_expenses_object == 'EXPENSE REPORT':
                    category_mapping = category_mapping.destination_expense_head
                else:
                    category_mapping = category_mapping.destination_account

        if not category_mapping:
            bulk_errors.append({
                'row': row,
                'expense_group_id': expense_group.id,
                'value': category,
                'type': 'Category Mapping',
                'message': 'Category Mapping Not Found'
            })

        row = row + 1

    return bulk_errors

def __validate_expense_group(expense_group: ExpenseGroup, configuration: Configuration):
    # General Mapping
    general_mapping_errors = __validate_general_mapping(expense_group, configuration)

    # Subsidiary Mapping
    subsidiary_mapping_errors = __validate_subsidiary_mapping(expense_group)

    # Employee Mapping
    employee_mapping_errors = __validate_employee_mapping(expense_group, configuration)

    # Category Mapping
    category_mapping_errors = __validate_category_mapping(expense_group, configuration)

    # Tax Group Mapping
    tax_group_mapping_errors = []
    if configuration.import_tax_items:
        tax_group_mapping_errors = __validate_tax_group_mapping(expense_group, configuration)

    bulk_errors = list(
        itertools.chain(
            general_mapping_errors, subsidiary_mapping_errors, employee_mapping_errors, category_mapping_errors, tax_group_mapping_errors
        )
    )

    if bulk_errors:
        raise BulkError('Mappings are missing', bulk_errors)

def schedule_bills_creation(workspace_id: int, expense_group_ids: List[str]):
    """
    Schedule bills creation
    :param expense_group_ids: List of expense group ids
    :param workspace_id: workspace id
    :return: None
    """
    if expense_group_ids:
        expense_groups = ExpenseGroup.objects.filter(
            Q(tasklog__id__isnull=True) | ~Q(tasklog__status__in=['IN_PROGRESS', 'COMPLETE']),
            workspace_id=workspace_id, id__in=expense_group_ids, bill__id__isnull=True, exported_at__isnull=True
        ).all()

        chain = Chain(cached=False)

        fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
        chain.append('apps.fyle.helpers.sync_dimensions', fyle_credentials, workspace_id)

        for expense_group in expense_groups:
            task_log, _ = TaskLog.objects.get_or_create(
                workspace_id=expense_group.workspace_id,
                expense_group=expense_group,
                defaults={
                    'status': 'ENQUEUED',
                    'type': 'CREATING_BILL'
                }
            )

            if task_log.status not in ['IN_PROGRESS', 'ENQUEUED']:
                task_log.type = 'CREATING_BILL'
                task_log.status = 'ENQUEUED'
                task_log.save()

            chain.append('apps.netsuite.tasks.create_bill', expense_group, task_log.id)

            task_log.save()
        if chain.length() > 1:
            chain.run()

def schedule_credit_card_charge_creation(workspace_id: int, expense_group_ids: List[str]):
    """
    Schedule Credit Card Charge creation
    :param expense_group_ids: List of expense group ids
    :param workspace_id: workspace id
    :return: None
    """
    if expense_group_ids:
        expense_groups = ExpenseGroup.objects.filter(
            Q(tasklog__id__isnull=True) | ~Q(tasklog__status__in=['IN_PROGRESS', 'COMPLETE']),
            workspace_id=workspace_id, id__in=expense_group_ids,
            creditcardcharge__id__isnull=True, exported_at__isnull=True
        ).all()

        chain = Chain(cached=False)

        fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
        chain.append('apps.fyle.helpers.sync_dimensions', fyle_credentials, workspace_id)

        for expense_group in expense_groups:
            expense_amount = expense_group.expenses.first().amount
            export_type = 'CREATING_CREDIT_CARD_CHARGE'
            if expense_amount < 0:
                export_type = 'CREATING_CREDIT_CARD_REFUND'

            task_log, _ = TaskLog.objects.get_or_create(
                workspace_id=expense_group.workspace_id,
                expense_group=expense_group,
                defaults={
                    'status': 'ENQUEUED',
                    'type': export_type
                }
            )

            if task_log.status not in ['IN_PROGRESS', 'ENQUEUED']:
                task_log.type = export_type
                task_log.status = 'ENQUEUED'
                task_log.save()

            chain.append('apps.netsuite.tasks.create_credit_card_charge', expense_group, task_log.id)

            task_log.save()
        if chain.length() > 1:
            chain.run()

def schedule_expense_reports_creation(workspace_id: int, expense_group_ids: List[str]):
    """
    Schedule expense reports creation
    :param expense_group_ids: List of expense group ids
    :param workspace_id: workspace id
    :return: None
    """
    if expense_group_ids:
        expense_groups = ExpenseGroup.objects.filter(
            Q(tasklog__id__isnull=True) | ~Q(tasklog__status__in=['IN_PROGRESS', 'COMPLETE']),
            workspace_id=workspace_id, id__in=expense_group_ids,
            expensereport__id__isnull=True, exported_at__isnull=True
        ).all()

        chain = Chain(cached=False)

        fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
        chain.append('apps.fyle.helpers.sync_dimensions', fyle_credentials, workspace_id)

        for expense_group in expense_groups:
            task_log, _ = TaskLog.objects.get_or_create(
                workspace_id=expense_group.workspace_id,
                expense_group=expense_group,
                defaults={
                    'status': 'ENQUEUED',
                    'type': 'CREATING_EXPENSE_REPORT'
                }
            )

            if task_log.status not in ['IN_PROGRESS', 'ENQUEUED']:
                task_log.type = 'CREATING_EXPENSE_REPORT'
                task_log.status = 'ENQUEUED'
                task_log.save()

            chain.append('apps.netsuite.tasks.create_expense_report', expense_group, task_log.id)
            task_log.save()
        if chain.length() > 1:
            chain.run()

def schedule_journal_entry_creation(workspace_id: int, expense_group_ids: List[str]):
    """
    Schedule journal entries creation
    :param expense_group_ids: List of expense group ids
    :param workspace_id: workspace id
    :return: None
    """
    if expense_group_ids:
        expense_groups = ExpenseGroup.objects.filter(
            Q(tasklog__id__isnull=True) | ~Q(tasklog__status__in=['IN_PROGRESS', 'COMPLETE']),
            workspace_id=workspace_id, id__in=expense_group_ids, journalentry__id__isnull=True, exported_at__isnull=True
        ).all()

        chain = Chain(cached=False)

        fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
        chain.append('apps.fyle.helpers.sync_dimensions', fyle_credentials, workspace_id)

        for expense_group in expense_groups:
            task_log, _ = TaskLog.objects.get_or_create(
                workspace_id=expense_group.workspace_id,
                expense_group=expense_group,
                defaults={
                    'status': 'ENQUEUED',
                    'type': 'CREATING_JOURNAL_ENTRY'
                }
            )

            if task_log.status not in ['IN_PROGRESS', 'ENQUEUED']:
                task_log.type = 'CREATING_JOURNAL_ENTRY'
                task_log.status = 'ENQUEUED'
                task_log.save()

            chain.append('apps.netsuite.tasks.create_journal_entry', expense_group, task_log.id)
            task_log.save()
        if chain.length() > 1:
            chain.run()

def check_expenses_reimbursement_status(expenses):
    all_expenses_paid = True

    for expense in expenses:
        reimbursement = Reimbursement.objects.filter(settlement_id=expense.settlement_id).first()

        if (reimbursement and reimbursement.state != 'COMPLETE') or not reimbursement:
            all_expenses_paid = False

    return all_expenses_paid

def create_netsuite_payment_objects(netsuite_objects, object_type, workspace_id):
    netsuite_payment_objects = {}

    netsuite_credentials = NetSuiteCredentials.objects.get(workspace_id=workspace_id)

    try:
        netsuite_connection = NetSuiteConnector(netsuite_credentials, workspace_id)
    except NetSuiteRateLimitError:
        logger.info('Rate limit error, workspace_id - %s', workspace_id)
        return
    except NetSuiteLoginError:
        logger.info('Invalid credentials, workspace_id - %s', workspace_id)
        return

    for netsuite_object in netsuite_objects:
        entity_id = netsuite_object.entity_id

        expense_group_reimbursement_status = check_expenses_reimbursement_status(
            netsuite_object.expense_group.expenses.all())

        netsuite_object_task_log = TaskLog.objects.get(
            expense_group=netsuite_object.expense_group, status='COMPLETE')

        # When the record is deleted, netsuite sdk would throw an exception RCRD_DSNT_EXIST
        try:
            if object_type == 'BILL':
                netsuite_entry = netsuite_connection.get_bill(netsuite_object_task_log.detail['internalId'])
            else:
                netsuite_entry = netsuite_connection.get_expense_report(netsuite_object_task_log.detail['internalId'])
        except Exception:
            netsuite_entry = None

        if netsuite_entry and netsuite_entry['status'] != netsuite_paid_state and expense_group_reimbursement_status:
            if entity_id not in netsuite_payment_objects:
                netsuite_payment_objects[entity_id] = {
                    'subsidiary_id': netsuite_object.subsidiary_id,
                    'entity_id': entity_id,
                    'currency': netsuite_object.currency,
                    'memo': 'Payment for {0} by {1}'.format(
                        object_type.lower(), netsuite_object.expense_group.description['employee_email']
                    ),
                    'unique_id': '{0}-{1}'.format(netsuite_object.external_id, netsuite_object.id),
                    'line': [
                        {
                            'internal_id': netsuite_object_task_log.detail['internalId'],
                            'entity_id': entity_id,
                            'expense_group': netsuite_object.expense_group,
                        }
                    ]
                }
            else:
                netsuite_payment_objects[entity_id]['line'].append(
                    {
                        'internal_id': netsuite_object_task_log.detail['internalId'],
                        'entity_id': entity_id,
                        'expense_group': netsuite_object.expense_group,
                    }
                )

    return netsuite_payment_objects

def process_vendor_payment(entity_object, workspace_id, object_type):
    task_log, _ = TaskLog.objects.update_or_create(
        workspace_id=workspace_id,
        task_id='PAYMENT_{}'.format(entity_object['unique_id']),
        defaults={
            'status': 'IN_PROGRESS',
            'type': 'CREATING_VENDOR_PAYMENT'
        }
    )

    try:
        with transaction.atomic():

            vendor_payment_object = VendorPayment.create_vendor_payment(
                workspace_id, entity_object
            )

            vendor_payment_lineitems = VendorPaymentLineitem.create_vendor_payment_lineitems(
                entity_object['line'], vendor_payment_object
            )

            netsuite_credentials = NetSuiteCredentials.objects.get(workspace_id=workspace_id)
            netsuite_connection = NetSuiteConnector(netsuite_credentials, workspace_id)

            first_object_id = vendor_payment_lineitems[0].doc_id
            if object_type == 'BILL':
                first_object = netsuite_connection.get_bill(first_object_id)
            else:
                first_object = netsuite_connection.get_expense_report(first_object_id)
            created_vendor_payment = netsuite_connection.post_vendor_payment(
                vendor_payment_object, vendor_payment_lineitems, first_object
            )

            lines = entity_object['line']
            expense_group_ids = [line['expense_group'].id for line in lines]

            if object_type == 'BILL':
                paid_objects = Bill.objects.filter(expense_group_id__in=expense_group_ids).all()

            else:
                paid_objects = ExpenseReport.objects.filter(expense_group_id__in=expense_group_ids).all()

            for paid_object in paid_objects:
                paid_object.payment_synced = True
                paid_object.paid_on_netsuite = True
                paid_object.save()

            task_log.detail = created_vendor_payment
            task_log.vendor_payment = vendor_payment_object
            task_log.status = 'COMPLETE'

            task_log.save()
    except NetSuiteCredentials.DoesNotExist:
        logger.info(
            'NetSuite Credentials not found for workspace_id %s',
            workspace_id
        )
        detail = {
            'message': 'NetSuite Account not connected'
        }
        task_log.status = 'FAILED'
        task_log.detail = detail

        task_log.save()

    except (NetSuiteRequestError, NetSuiteLoginError) as exception:
        all_details = []
        logger.info({'error': exception})
        detail = json.dumps(exception.__dict__)
        detail = json.loads(detail)
        task_log.status = 'FAILED'

        all_details.append({
            'value': netsuite_error_message,
            'type': detail['code'],
            'message': detail['message']
        })
        task_log.detail = all_details

        task_log.save()

    except NetSuiteRateLimitError:
        logger.info('Rate limit error, workspace_id - %s', workspace_id)
        task_log.status = 'FAILED'
        task_log.detail = {
            'error': 'Rate limit error'
        }

        task_log.save()

    except BulkError as exception:
        logger.info(exception.response)
        detail = exception.response
        task_log.status = 'FAILED'
        task_log.detail = detail

        task_log.save()

def create_vendor_payment(workspace_id):
    fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)

    try:
        platform = PlatformConnector(fyle_credentials=fyle_credentials)
    except InvalidTokenError:
        logger.info('Invalid Fyle refresh token for workspace %s', workspace_id)
        return
    platform.reimbursements.sync()

    bills = Bill.objects.filter(
        payment_synced=False, expense_group__workspace_id=workspace_id,
        expense_group__fund_source='PERSONAL', expense_group__exported_at__isnull=False
    ).all()

    expense_reports = ExpenseReport.objects.filter(
        payment_synced=False, expense_group__workspace_id=workspace_id,
        expense_group__fund_source='PERSONAL', expense_group__exported_at__isnull=False
    ).all()

    if bills:
        bill_entity_map = create_netsuite_payment_objects(bills, 'BILL', workspace_id)

        for entity_object_key in bill_entity_map:
            entity_id = entity_object_key
            entity_object = bill_entity_map[entity_id]

            process_vendor_payment(entity_object, workspace_id, 'BILL')

    if expense_reports:
        expense_report_entity_map = create_netsuite_payment_objects(
            expense_reports, 'EXPENSE REPORT', workspace_id)

        for entity_object_key in expense_report_entity_map:
            entity_id = entity_object_key
            entity_object = expense_report_entity_map[entity_id]

            process_vendor_payment(entity_object, workspace_id, 'EXPENSE REPORT')

def schedule_vendor_payment_creation(sync_fyle_to_netsuite_payments, workspace_id):
    general_mappings: GeneralMapping = GeneralMapping.objects.filter(workspace_id=workspace_id).first()
    if general_mappings and sync_fyle_to_netsuite_payments and general_mappings.vendor_payment_account_id:
        start_datetime = datetime.now()
        schedule, _ = Schedule.objects.update_or_create(
            func='apps.netsuite.tasks.create_vendor_payment',
            args='{}'.format(workspace_id),
            defaults={
                'schedule_type': Schedule.MINUTES,
                'minutes': 24 * 60,
                'next_run': start_datetime
            }
        )
    if not sync_fyle_to_netsuite_payments:
        schedule: Schedule = Schedule.objects.filter(
            func='apps.netsuite.tasks.create_vendor_payment',
            args='{}'.format(workspace_id)
        ).first()

        if schedule:
            schedule.delete()

def get_all_internal_ids(netsuite_objects):
    netsuite_objects_details = {}

    expense_group_ids = [netsuite_object.expense_group_id for netsuite_object in netsuite_objects]

    task_logs = TaskLog.objects.filter(expense_group_id__in=expense_group_ids).all()
    for task_log in task_logs:
        netsuite_objects_details[task_log.expense_group.id] = {
            'expense_group': task_log.expense_group,
            'internal_id': task_log.detail['internalId']
        }

    return netsuite_objects_details

def check_netsuite_object_status(workspace_id):
    netsuite_credentials = NetSuiteCredentials.objects.get(workspace_id=workspace_id)

    try:
        netsuite_connection = NetSuiteConnector(netsuite_credentials, workspace_id)
    except NetSuiteRateLimitError:
        logger.info('Rate limit error, workspace_id - %s', workspace_id)
        return
    except NetSuiteLoginError:
        logger.error('NetSuite login error, workspace_id - %s', workspace_id)
        return

    bills = Bill.objects.filter(
        expense_group__workspace_id=workspace_id, paid_on_netsuite=False, expense_group__fund_source='PERSONAL'
    ).all()

    expense_reports = ExpenseReport.objects.filter(
        expense_group__workspace_id=workspace_id, paid_on_netsuite=False, expense_group__fund_source='PERSONAL'
    ).all()

    if bills:
        internal_ids = get_all_internal_ids(bills)
        for bill in bills:
            try:
                bill_object = netsuite_connection.get_bill(internal_ids[bill.expense_group.id]['internal_id'])
                if bill_object['status'] == netsuite_paid_state:
                    line_items = BillLineitem.objects.filter(bill_id=bill.id)
                    for line_item in line_items:
                        expense = line_item.expense
                        expense.paid_on_netsuite = True
                        expense.save()

                    bill.paid_on_netsuite = True
                    bill.payment_synced = True
                    bill.save()
            except NetSuiteRequestError as exception:
                logger.info({'error': exception})
                pass

    if expense_reports:
        internal_ids = get_all_internal_ids(expense_reports)

        for expense_report in expense_reports:
            try:
                expense_report_object = netsuite_connection.get_expense_report(
                    internal_ids[expense_report.expense_group.id]['internal_id'])
                if expense_report_object['status'] == netsuite_paid_state:
                    line_items = ExpenseReportLineItem.objects.filter(expense_report_id=expense_report.id)
                    for line_item in line_items:
                        expense = line_item.expense
                        expense.paid_on_netsuite = True
                        expense.save()

                    expense_report.paid_on_netsuite = True
                    expense_report.payment_synced = True
                    expense_report.save()
            except NetSuiteRequestError as exception:
                logger.info({'error': exception})
                pass

def schedule_netsuite_objects_status_sync(sync_netsuite_to_fyle_payments, workspace_id):
    if sync_netsuite_to_fyle_payments:
        start_datetime = datetime.now()
        schedule, _ = Schedule.objects.update_or_create(
            func='apps.netsuite.tasks.check_netsuite_object_status',
            args='{}'.format(workspace_id),
            defaults={
                'schedule_type': Schedule.MINUTES,
                'minutes': 24 * 60,
                'next_run': start_datetime
            }
        )
    else:
        schedule: Schedule = Schedule.objects.filter(
            func='apps.netsuite.tasks.check_netsuite_object_status',
            args='{}'.format(workspace_id)
        ).first()

        if schedule:
            schedule.delete()

def get_valid_reimbursement_ids(reimbursement_ids: List, platform: PlatformConnector) -> List[str]:
    chunk_size = 10
    count_of_reimbursements = len(reimbursement_ids)

    valid_reimbursement_ids = []
    for index in range(0, count_of_reimbursements, chunk_size):
        partitioned_list = reimbursement_ids[index:index + chunk_size]

        id_filter = 'in.{}'.format(tuple(partitioned_list)).replace('\'', '"') \
            if len(partitioned_list) > 1 else 'eq.{}'.format(partitioned_list[0])

        query_params = {
            'id': id_filter,
            'is_paid': 'eq.false'
        }

        reimbursements = platform.reimbursements.search_reimbursements(query_params)

        for reimbursements_generator in reimbursements:
            valid_ids = [reimbursement['id'] for reimbursement in reimbursements_generator['data']]
            valid_reimbursement_ids.extend(valid_ids)

    return valid_reimbursement_ids

def process_reimbursements(workspace_id):
    fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)

    try:
        platform = PlatformConnector(fyle_credentials=fyle_credentials)
    except InvalidTokenError:
        logger.info('Invalid Fyle refresh token for workspace %s', workspace_id)
        return

    platform.reimbursements.sync()

    reimbursements = Reimbursement.objects.filter(state='PENDING', workspace_id=workspace_id).all()

    reimbursement_ids = []

    if reimbursements:
        for reimbursement in reimbursements:
            expenses = Expense.objects.filter(settlement_id=reimbursement.settlement_id, fund_source='PERSONAL').all()
            paid_expenses = expenses.filter(paid_on_netsuite=True)

            all_expense_paid = False
            if len(expenses):
                all_expense_paid = len(expenses) == len(paid_expenses)

            if all_expense_paid:
                reimbursement_ids.append(reimbursement.reimbursement_id)

    if reimbursement_ids:
        # Validating deleted reimbursements
        valid_reimbursement_ids = get_valid_reimbursement_ids(reimbursement_ids, platform)

        chunk_size = 20

        for index in range(0, len(valid_reimbursement_ids), chunk_size):
            partitioned_list = valid_reimbursement_ids[index:index + chunk_size]

            if partitioned_list:
                reimbursements_list = []
                for reimbursement_id in partitioned_list:
                    reimbursement_object = {'id': reimbursement_id}
                    reimbursements_list.append(reimbursement_object)

                try:
                    platform.reimbursements.bulk_post_reimbursements(reimbursements_list)
                    platform.reimbursements.sync()
                except Exception as error:
                    error = traceback.format_exc()
                    error = {
                        'error': error
                    }
                    logger.exception(error)

def schedule_reimbursements_sync(sync_netsuite_to_fyle_payments, workspace_id):
    if sync_netsuite_to_fyle_payments:
        start_datetime = datetime.now() + timedelta(hours=12)
        schedule, _ = Schedule.objects.update_or_create(
            func='apps.netsuite.tasks.process_reimbursements',
            args='{}'.format(workspace_id),
            defaults={
                'schedule_type': Schedule.MINUTES,
                'minutes': 24 * 60,
                'next_run': start_datetime
            }
        )
    else:
        schedule: Schedule = Schedule.objects.filter(
            func='apps.netsuite.tasks.process_reimbursements',
            args='{}'.format(workspace_id)
        ).first()

        if schedule:
            schedule.delete()


from typing import Coroutine, NamedTuple, List, Dict, Optional
from random import choice, randint, random
from string import ascii_uppercase
from csp import CSP, Constraint

Grid = List[List[str]]  # type alias for grids

class Colors:
    CBLACK = "\33[30m"
    CRED = "\33[31m"
    CGREEN = "\33[32m"
    CYELLOW = "\33[33m"
    CBLUE = "\33[34m"
    CVIOLET = "\33[35m"
    CBEIGE = "\33[36m"
    CWHITE = "\33[37m"
    ENDC = "\033[0m"

    @classmethod
    def random_color(self) -> str:
        return choice(
            [
                self.CBLACK,
                self.CBLUE,
                self.CGREEN,
                self.CRED,
                self.CVIOLET,
                self.CYELLOW,
                self.CBEIGE,
            ]
        )

def colored_letter(letter: str, color: str) -> str:
    return color + letter + Colors.ENDC

class GridLocation(NamedTuple):
    row: int
    column: int

def generate_grid(rows: int, columns: int) -> Grid:
    # initialize grid with random letters
    return [[choice(ascii_uppercase) for c in range(columns)] for r in range(rows)]

def display_grid(grid: Grid) -> None:
    for row in grid:
        print("".join(row))
    print()

def generate_domain(word: str, grid: Grid) -> List[List[GridLocation]]:
    domain: List[List[GridLocation]] = []
    height: int = len(grid)
    width: int = len(grid[0])
    length: int = len(word)
    for row in range(height):
        for col in range(width):
            columns: range = range(col, col + length + 1)
            rows: range = range(row, row + length + 1)
            if col + length <= width:
                # left to right
                domain.append([GridLocation(row, c) for c in columns])
                # diagonal towards bottom right
                if row + length <= height:
                    domain.append([GridLocation(r, col + (r - row)) for r in rows])
            if row + length <= height:
                # top to bottom
                domain.append([GridLocation(r, col) for r in rows])
                # diagonal towards bottom left
                if col - length >= 0:
                    domain.append([GridLocation(r, col - (r - row)) for r in rows])

        return domain

class WordSearchConstraint(Constraint[str, List[GridLocation]]):
    def __init__(self, words: List[str]) -> None:
        super().__init__(words)
        self.words: List[str] = words

    def satisfied(self, assignment: Dict[str, List[GridLocation]]) -> bool:
        # if there are any duplicates grid locations, then there is an overlap
        all_locations = [locs for values in assignment.values() for locs in values]
        return len(set(all_locations)) == len(all_locations)

if __name__ == "__main__":
    grid: Grid = generate_grid(9, 9)
    print("grid before: ")
    display_grid(grid)
    words: List[str] = ["MATTHEW", "JOE", "MARY", "SARAH", "SALLY"]
    word_colors: List[str] = [
        Colors.CBEIGE,
        Colors.CBLUE,
        Colors.CGREEN,
        Colors.CVIOLET,
        Colors.CYELLOW,
        Colors.CRED,
    ]
    print("words:\n", words)
    locations: Dict[str, List[List[GridLocation]]] = {}
    for word in words:
        locations[word] = generate_domain(word, grid)
    csp: CSP[str, List[GridLocation]] = CSP(words, locations)
    csp.add_constraint(WordSearchConstraint(words))
    solution: Optional[Dict[str, List[GridLocation]]] = csp.backtracking_search()
    if solution is None:
        print("No solution found!")
    else:
        for word, grid_locations in solution.items():
            # random reverse half the time
            if choice([True, False]):
                grid_locations.reverse()
            for index, letter in enumerate(word):
                (row, col) = (grid_locations[index].row, grid_locations[index].column)
                grid[row][col] = colored_letter(
                    letter=letter, color=word_colors[words.index(word)]
                )
        print("\nlater:\n")
        display_grid(grid)


class Solution(object):
    # 枚举法 O(n^3)
    def numTeams(self, rating):
        """
        :type rating: List[int]
        :rtype: int
        """
        if len(rating) < 3:
            return 0
        res = 0
        for i in range(len(rating)-2):
            for j in range(i+1, len(rating)-1):
                for k in range(j+1,len(rating)):
                    if rating[i] < rating[j] and rating[j] < rating[k] or rating[i] > rating[j] and rating[j] > rating[k]:
                        res += 1
        return res

    # 状态压缩 利用乘法原理 O(n^2)
    def numTeams(self, rating):
        """
        :type rating: List[int]
        :rtype: int
        """
        n = len(rating)
        res = 0
        for j in range(n):
            l = 0
            r = 0
            for i in range(j):
                if rating[i] < rating[j]:
                    l += 1
            for k in range(j+1, n):
                if rating[j] < rating[k]:
                    r += 1
            res += l*r + (j-l)*(n-j-1-r)
        return res


__author__ = 'Administrator'
background_image_file = 'background.bmp'
screen_size = (800, 600)
message = 'this is a message from python console'

import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode(screen_size, 0, 32)

font = pygame.font.SysFont('arial', 80)
text_surface = font.render(message, True, (0, 0, 255))

x = 0
y = (screen_size[1] - text_surface.get_height()) / 2


from enum import Enum
import random
import curses
from curses import wrapper
from time import sleep
import time

class Direction(Enum):
    UNKWOWN = 0
    UP = 1
    DOWN = 2
    RIGHT = 3
    LEFT = 4

# .ooooo0
# нарисовать окно
class Snake:
    def __init__(self):
        self.direction = Direction.UNKWOWN
        # (x, y) (0 - head) (2 - tail)
        self.body = [(10, 10), (9, 10), (8, 10)]

    def move(self):
        if self.direction == Direction.RIGHT:
            body_head = self.body[0]
            x = body_head[0]
            y = body_head[1]
            self.body.insert(0, (x + 1, y))
            self.body.pop()
        if self.direction == Direction.LEFT:
            self.body.insert(0, (self.body[0][0] - 1, self.body[0][1]))
            self.body.pop()
        if self.direction == Direction.UP:
            self.body.insert(0, (self.body[0][0], self.body[0][1] - 1))
            self.body.pop()
        if self.direction == Direction.DOWN:
            self.body.insert(0, (self.body[0][0], self.body[0][1] + 1))
            self.body.pop()

    def set_direction(self, direction):
        self.direction = direction

    def eat(self):
        pass

    def is_head(self, x, y):
        if x == self.body[0][0] and y == self.body[0][1]:
            return True
        else:
            return False

    def is_tail(self, x, y):
        if x == self.body[-1][0] and y == self.body[-1][1]:
            return True
        else:
            return False

    def is_body(self, x, y):
        for x1, y1 in self.body[1 : -1]:
            if x == x1 and y == y1:
                return True
        return False

def create_apple(snake, hight, width):
    y = random.randint(0, hight)
    x = random.randint(0, width)
    for x1, y1 in snake.body:
        if x1 == x and y1 == y:
            return create_apple(snake, hight, width)
    return (x, y)

def eat_apple(snake, apple):
    x = apple[0]
    y = apple[1]
    for x1, y1 in snake.body:
        if x == x1 and y == y1:
            apple = None

# дано: y=9, x=10; x1=10, y1=10

# snake = None

def main(stdscr):
    global snake
    curses.curs_set(False)
    curses.cbreak()
    stdscr.keypad(True)
    curses.noecho()
    stdscr.nodelay(True)
    prev_time = time.time()
    last_key = -1
    while True:
        curr_time = time.time()
        if curr_time - prev_time >= 0.5:
            snake.move()
            prev_time = curr_time
        stdscr.clear()
        stdscr.addstr(snake.body[0][1], snake.body[0][0], '0')
        stdscr.addstr(snake.body[-1][1], snake.body[-1][0], '.')
        for cell in snake.body[1:-1]:
            stdscr.addstr(cell[1], cell[0], 'o')

        key = stdscr.getch()
        if key != -1:
            last_key = key

        stdscr.addstr(0, 0, f'{last_key}')
        if last_key == 10:
            break
        elif last_key == 454:
            snake.set_direction(Direction.RIGHT)
        elif last_key == 456:
            snake.set_direction(Direction.DOWN)
        elif last_key == 452:
            snake.set_direction(Direction.LEFT)
        elif last_key == 450:
            snake.set_direction(Direction.UP)

        stdscr.refresh()

    # stdscr.addstr(0, 0, 'A')
    # x = 0
    # x2 = 0
    # count = 0
    # curses.curs_set(False)
    # while True:
    #     count = count + 1
    #     if count % 2 == 0:
    #         x = x + 1
    #     stdscr.clear()
    #     stdscr.addstr(0, x, 'A')
    #     x2 = x2 + 1
    #     stdscr.addstr(1, x2, 'B')
    #     stdscr.refresh()
    #     sleep(0.5)

    stdscr.getkey()

# создать яблоко -> съесть его -> создать новое яблоко

if __name__ == '__main__':

    global snake
    snake = Snake()
    snake.set_direction(Direction.DOWN)
    # apple = create_apple(snake, 20, 20)
    # for y in range(0 , 20):
    #    for x in range(0, 20):
    #        if x == apple[0] and y == apple[1]:
    #            print('Q', end = '')
    #         elif snake.is_head(x, y):
    #             print('0', end = '')
    #         elif snake.is_tail(x, y):
    #             print('.', end = '')
    #         elif snake.is_body(x, y):
    #             print('o', end = '')
    #         else:
    #             print(' ', end = '')
    #     print()
    # stdscr = curses.initscr()
    # begin_x = 0
    # begin_y = 0
    # weight_field = 20
    # height_field = 20
    # win = curses.newwin(weight_field, height_field, begin_y, begin_x)
    # win.getkey()
    # wrapper

    wrapper(main)


from typing import Callable, Optional
import cherrypy

Dispatcher = Callable[[str], None]

class HostDispatcher:
    """
    Dispatcher that performs hostname validation.
    """

    def __init__(self, host: Optional[str] = None, port: int = 80,
                  next_dispatcher: Optional[Dispatcher] = None):
        if next_dispatcher is None:
            next_dispatcher = cherrypy.dispatch.Dispatcher()
        self._next_dispatcher = next_dispatcher

        self._domain: Optional[str] = None
        if host is not None and ':' not in host and port != 80:
            self._domain = f'{host}:{port}'
        elif host is not None:
            self._domain = host

    @property
    def next_dispatcher(self) -> Dispatcher:
        """
        Retrieve the next dispatcher in line to be used if the host validation
        succeeds.
        """

        return self._next_dispatcher

    @property
    def domain(self) -> Optional[str]:
        """
        Retrieve the expected domain name for the host.
        """

        return self._domain

    def __call__(self, path_info: str) -> None:
        if self._domain is None:
            return self._next_dispatcher(path_info)

        request = cherrypy.serving.request
        host = request.headers.get('Host', '')
        if host == self._domain:
            return self._next_dispatcher(path_info)

        request.config = cherrypy.config.copy()
        request.handler = cherrypy.HTTPError(403, 'Invalid Host header')
        return None


import json
import csv
import math
from random import randint
from typing import Callable

'''
Напишите следующие функции:
○ Нахождение корней квадратного уравнения
○ Генерация csv файла с тремя случайными числами в каждой строке.
100-1000 строк.
○ Декоратор, запускающий функцию нахождения корней квадратного
уравнения с каждой тройкой чисел из csv файла.
○ Декоратор, сохраняющий переданные параметры и результаты работы
функции в json файл.
'''

def quadratic_equation(a: int, b: int, c: int) -> (float, float):
    discr = b ** 2 - 4 * a * c
    print(f'Дискриминант = {discr:.2f}')

    if discr > 0:
        x1 = (-b + math.sqrt(discr)) / (2 * a)
        x2 = (-b - math.sqrt(discr)) / (2 * a)
        return f'{x1:.2f}, {x2:.2f}'
    elif discr == 0:
        x = -b / (2 * a)
        return f'{x:.2f}'
    else:
        return 'Корней нет'

def num_csv_gen(file_name):
    with open(file_name, 'w', newline='') as csv_f:
        writer = csv.writer(csv_f)
        for _ in range(randint(100, 1000)):
            row = [randint(1, 20) for _ in range(3)]
            writer.writerow(row)

def deco_roots(func: Callable):
    def wrapper(csv_file):
        results = []
        with open(csv_file, 'r', newline='') as csv_f:
            reader = csv.reader(csv_f)
            for row in reader:
                a, b, c = map(int, row)
                roots = func(a, b, c)
                results.append((a, b, c, roots))
                print(f"Корни уравнения {a}x^2 + {b}x + {c} = 0: {roots}")
            return results

    return wrapper

def save_to_json_deco(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        data = {
            'function_name': func.__name__,
            'arguments': {
                'args': args,
                'kwargs': kwargs
            },
            'result': result
        }
        file_name = f'{func.__name__}_results.json'
        with open(file_name, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=4)
        print(f'Результаты сохранены в файл {file_name}')
        return result

    return wrapper

@save_to_json_deco
@deco_roots
def find_roots(a, b, c):
    return quadratic_equation(a, b, c)

csv_file = 'random_numbers.csv'
num_csv_gen(csv_file)

find_roots(csv_file)


#!/usr/bin/python

import sys

LANGUAGES = ['fi', 'sv', 'en']
TSV_FILE_PREFIX = 'Exam-kielipaketti - '
JSON_FILE_PREFIX = '/public/assets/languages/locale-'

def parse_opts():
    if len(sys.argv) < 3:
        raise Exception("usage: " + sys.argv[0] + " <input_dir> <output_dir>")
    return sys.argv[1], sys.argv[2]

def read_file(input_dir, lang):
    file_name = input_dir + "/" + TSV_FILE_PREFIX + lang + '.tsv'
    f = open(file_name)
    return f.read()

def tsv_to_json(input):
    lines = input.split('\r\n')
    objs = []
    for line in lines:
        key, value = line.split('\t')[:2]
        objs.append((key.strip(), value.strip()))

    fields = ['"{0}": "{1}"'.format(obj[0], obj[1].replace('"', '\\"')) for obj in objs]
    return '{\r\n  ' + ',\n  '.join(fields) + '\r\n}\r\n'

def write(content, dir, lang):
    file_name = dir + JSON_FILE_PREFIX + lang + ".json"
    f = open(file_name, 'w')
    f.truncate()
    f.write(content)
    f.close()

def main():
    input_dir, output_dir = parse_opts()
    for lang in LANGUAGES:
        tsv_content = read_file(input_dir, lang)
        json_content = tsv_to_json(tsv_content)
        write(json_content, output_dir,lang)

if __name__ == "__main__":
    main()


import torch
import torch.nn.functional as F
import numpy as np

from .base import BaseNetwork
from .utils import noisy_l, init_weights, orthogonal_init

class RainbowIQN(BaseNetwork):
    def __init__(
        self, D_in, D_out, D_em, N_sample, noise_type, D_hidden=512, head="mlp"
    ):
        D_head_out = super(RainbowIQN, self).__init__(D_in, D_hidden, head)
        self.D_out = D_out
        self.noise_type = noise_type

        self.N_sample = N_sample
        self.i_pi = (torch.arange(0, D_em) * np.pi).view(1, 1, D_em)

        self.state_embed = torch.nn.Linear(D_head_out, D_hidden)
        self.sample_embed = torch.nn.Linear(D_em, D_hidden)

        self.l1 = torch.nn.Linear(D_hidden, D_hidden)
        self.l2 = torch.nn.Linear(D_hidden, D_hidden)

        self.mu_w_a1, self.sig_w_a1, self.mu_b_a1, self.sig_b_a1 = init_weights(
            (D_hidden, D_hidden), noise_type
        )
        self.mu_w_v1, self.sig_w_v1, self.mu_b_v1, self.sig_b_v1 = init_weights(
            (D_hidden, D_hidden), noise_type
        )

        self.mu_w_a2, self.sig_w_a2, self.mu_b_a2, self.sig_b_a2 = init_weights(
            (D_hidden, self.D_out), noise_type
        )
        self.mu_w_v2, self.sig_w_v2, self.mu_b_v2, self.sig_b_v2 = init_weights(
            (D_hidden, 1), noise_type
        )

        orthogonal_init([self.sample_embed, self.sample_embed, self.l1, self.l2])

    def forward(self, x, is_train, tau_min=0, tau_max=1):
        x = super(RainbowIQN, self).forward(x)
        state_embed = F.relu(self.state_embed(x))

        cos_term, tau = self.make_embed(x, tau_min, tau_max)
        tau_embed = F.relu(self.sample_embed(cos_term))

        embed = torch.unsqueeze(state_embed, 1) * tau_embed

        x = F.relu(self.l1(embed))
        x = F.relu(self.l2(x))

        x_a = F.relu(
            noisy_l(
                x,
                self.mu_w_a1,
                self.sig_w_a1,
                self.mu_b_a1,
                self.sig_b_a1,
                self.noise_type,
                is_train,
            )
        )
        x_v = F.relu(
            noisy_l(
                x,
                self.mu_w_v1,
                self.sig_w_v1,
                self.mu_b_v1,
                self.sig_b_v1,
                self.noise_type,
                is_train,
            )
        )

        # A stream : action advantage
        x_a = noisy_l(
            x_a,
            self.mu_w_a2,
            self.sig_w_a2,
            self.mu_b_a2,
            self.sig_b_a2,
            self.noise_type,
            is_train,
        )  # [bs, num_sample, num_action]
        x_a_mean = x_a.mean(dim=2).unsqueeze(2)  # [bs, num_sample, 1]
        x_a = x_a - x_a_mean.repeat(1, 1, self.D_out)  # [bs, num_sample, num_action]

        # V stream : state value
        x_v = noisy_l(
            x_v,
            self.mu_w_v2,
            self.sig_w_v2,
            self.mu_b_v2,
            self.sig_b_v2,
            self.noise_type,
            is_train,
        )  # [bs, num_sample, 1]
        x_v = x_v.repeat(1, 1, self.D_out)  # [bs, num_sample, num_action]

        out = x_a + x_v  # [bs, num_sample, num_action]

        return out, tau

    def make_embed(self, x, tau_min, tau_max):
        tau = (
            torch.FloatTensor(x.size(0), self.N_sample, 1)
            .uniform_(tau_min, tau_max)
            .to(x.device)
        )
        embed = torch.cos(tau * self.i_pi.to(x.device))
        return embed, tau


from alien.vstruct import StructBase
from alien.raw import RawData
from alien.octal import OctalNumber
from alien.timestamp import Timestamp

#----------------------------------------------------------------------------
# TarHeader
#----------------------------------------------------------------------------
class TarHeader(StructBase):
    __format__ = {
        0 : [
        ('fileName', RawData(100)),
        ('fileMode', RawData(8)),
        ('ownerId', OctalNumber(8)),
        ('groupId', OctalNumber(8)),
        ('fileSize', OctalNumber(12)),
        ('modTime', Timestamp(OctalNumber(12))),
        ('checksum', RawData(8)),
        ('typeFlag', RawData(1)),
        ('linkTarget', RawData(100)),
        ('ustarMagic', RawData(6)),
        ('ustarVersion', RawData(2)),
        ('ownerName', RawData(32)),
        ('groupName', RawData(32)),
        ('deviceMajor', OctalNumber(8)),
        ('deviceMinor', OctalNumber(8)),
        ('filePrefix', RawData(155)),
        ('pad', RawData(12)),
        ],
    }

__all__ = ['TarHeader']


from tensorflow.python.keras.models import load_model
from data_sequence import DatasetSequence
import glob
import PIL.Image as Image
from matplotlib import pyplot as plt
from matplotlib import cm as CM
import os
import numpy as np

def save_density_map(density_map, name):
    plt.figure(dpi=600)
    plt.axis('off')
    plt.margins(0, 0)
    plt.imshow(density_map, cmap=CM.jet)
    plt.savefig(os.path.join("experiment", name), dpi=600, bbox_inches='tight', pad_inches=0)

if __name__ == "__main__":
    MODEL_PATH = "experiment/single_image_random_crop_experiment_model_6.model"
    DATA_PATH = "/data/cv_data/UCFCrowdCountingDataset_CVPR13_with_people_density_map/UCF_CC_50/"

    image_list = glob.glob(DATA_PATH+"*.jpg")
    density_list = list(map(lambda s: s.replace(".jpg", ".h5"), image_list))

    dataset = DatasetSequence(image_list, density_list, random_crop_size=(224, 224))

    img_train, density_train = dataset.get_random_crop_image(0)
    pil_img = Image.fromarray(img_train[0])
    pil_img.save("experiment/train.png")

    model = load_model(MODEL_PATH)

    print("label ", density_train.sum())
    save_density_map(np.squeeze(density_train[0], axis=2), "label.png")

    pred = model.predict(img_train)

    save_density_map(np.squeeze(pred[0], axis=2), "predict.png")
    print("predict ", np.squeeze(pred[0], axis=2).shape, np.squeeze(pred[0], axis=2).sum())

    print("------------")


"""
    Pipeline for inverting the predicted labelmaps (i.e. the outputs of the CNN),
    in the original subject space (i.e. before registration).

    Inverse deformable transformation is calculated using DRAMMS.

"""

import os
from os.path import join as jp
import numpy as np
from glob import glob
import nibabel as nib
from nilearn.image import new_img_like
import time
from scripts.registration.dramms import transf_label

# Path variables for CNN predictions
cnn_path = '/.../'

# Path variables for original data
data_path = '/.../'
data = sorted(glob(os.path.join(data_path, '*')))
data_names = sorted(os.listdir(data_path))

# Load predicted labelmaps (i.e. output of CNN)
cnn_labelmaps = np.load(jp(cnn_path, 'predicted_masks.npy'))  # shape (num_of_images, 64, 128, 160, num_of_labels)

start_time = time.time()

for i in range(0, len(data)):

    target = data_names[i]
    print('Patient', target)

    if 1:  # Convert predictions to labelmaps

        predicted_cnn = cnn_labelmaps[i]

        # Convert predicted softmax labelmap into binary
        predicted_bin = np.where(predicted_cnn > 0.5, 1, 0)

        # Convert from one-hot back into labelmaps with values  [0 1 2 3 4 5]
        predicted_labelmap = np.argmax(predicted_cnn, axis=-1)

        # Save predicted output as nifti
        reg_labelmap = nib.load(jp(data_path, target, ''.join((target, '_deform.nii.gz'))))

        predicted_labelmap_nii = new_img_like(reg_labelmap, predicted_labelmap, copy_header=True)
        predicted_labelmap_nii.to_filename(jp(data_path, target, ''.join((target, '_predicted_labelmap.nii.gz'))))

    if 1:  # Invert predicted labelmap to original image space

        predicted_labelmap = jp(data_path, target, ''.join((target, '_predicted_labelmap.nii.gz')))
        reference_mri = jp(data_path, target, ''.join((target, '.nii.gz')))
        inverse_labelmap = jp(data_path, target, ''.join((target, '_predicted_labelmap_inv.nii.gz')))
        inv_tot_transf = jp(data_path, target, ''.join((target, '_inv_total_transf.nii.gz')))

        # Invert labelmap
        transf_label(predicted_labelmap, inv_tot_transf, inverse_labelmap, reference_mri)

        print('Predicted output is inverted to original image space')

    if 1:  # Invert deform mris to original image space

        deform_mri = jp(data_path, target, ''.join((target, '_deform.nii.gz')))
        inverse_mri = jp(data_path, target, ''.join((target, '_inverse.nii.gz')))
        inv_tot_transf = jp(data_path, target, ''.join((target, '_inv_total_transf.nii.gz')))

        # Invert mri
        transf_label(deform_mri, inv_tot_transf, inverse_mri, reference_mri)

        print('Deformed MRI is inverted to original image space')


from django.contrib import admin
from . import models

class QuestionAdmin(admin.ModelAdmin):
    singular_message_bit = "1 questions was"
    plural_message_bit = "{} questions were"
    def mark_selected_as_published(self, request, queryset):
        rows_updated = queryset.update(published=True)
        if rows_updated == 1:
            message_bit = self.singular_message_bit
        else:
            message_bit = self.plural_message_bit.format(rows_updated)
        self.message_user(request, "%s successfully marked as published." % message_bit)

    def mark_selected_as_unpublished(self, request, queryset):
        rows_updated = queryset.update(published=False)
        if rows_updated == 1:
            message_bit = self.singular_message_bit
        else:
            message_bit = self.plural_message_bit.format(rows_updated)
        self.message_user(request, "{} successfully marked as unpublished.".format(message_bit))

    list_display = ("get_course_code", "quiz_number", "question_type", "pub_date", "published")
    list_filter = ["published", "pub_date", "question_type", "course"]
    actions = [
                "mark_selected_as_published",
                "mark_selected_as_unpublished"
            ]
    fieldsets = [
                    ("detail", {"fields": [
                                    "course",
                                    "date",
                                    "question_type",
                                    "quiz_number",
                                    "pub_date",
                                    "published"]}),
                    ("question files", {"fields": [
                                        "question_file",
                                        "solution_file"
                                            ]}),
                    ("question", {"fields": [
                                    "page1",
                                    "page2",
                                    "page3",
                                    "page4"]})
                ]

class CollegeAdmin(admin.ModelAdmin):
    list_display = ["id","name"]
class CourseAdmin(admin.ModelAdmin):
    list_display = ["code","title","id"]

admin.site.register(models.College,CollegeAdmin)
admin.site.register(models.Programme)
admin.site.register(models.Course,CourseAdmin)
admin.site.register(models.Question,QuestionAdmin)


# -*- coding: utf-8 -*-

from functools import reduce
import math
from prettytable import PrettyTable
from nltk.corpus import stopwords
import sys
from downloadPages import downloadPages
import trie

useless_char = " .,!@#$%^&*();:\n\t\\\"?!{}[]<>"  # Strip these characters in the document
useless_words = set(stopwords.words('english'))

# take an input query and search for all web pages that contain this query ( lsit them by relativity)
def search():
    t = PrettyTable(['Match Score', 'Web page'])

    query = input("Input query or input exit:  ")
    if query == "exit":
        sys.exit()

    query = query.lower().split()
    if not query:
        print("Warning: Your input cannot be empty, please input again!\n")
        return

    query = [q.strip(useless_char) for q in query]
    query = [i for i in query if i not in useless_words]
    unique_query = set([i for i in query if len(i) != 0])
    if not unique_query: # no useful information in query
        print("Warning: Your input dese not conatin any useful information, please input again!\n")
        return

    else:
        # store numerous dictionaries, each dictionary store a word's appear times in every page.
        frequency = {}
        for word in unique_query:
            frequency[word] = (trie.findPages(word))

        sets = [set(f.keys()) for f in frequency.values()]
        unique_page_id = reduce(set.union, [s for s in sets])
        if not unique_page_id:
            print ("Sorry! No matching documents\n")
        else:
            print (str(len(unique_page_id))+" documents matched")
            scores = sorted([(id,similarity(unique_query,id, frequency))for id in unique_page_id], key=lambda x: x[1],reverse=True)
            for (id,score) in scores:
                t.add_row([round(score, 4), pages[id].strip('.txt').split('/pages/')[1]])
            print(t)
            print()

def similarity(query,id,frequency):
    similarity = 0.0
    for word in query:
        if word in dictionary and id in trie.findPages(word):
                inverseFrequency = math.log(len(pages) / len(frequency[word]), 2)
                importance = frequency[word][id]*inverseFrequency
                similarity += importance**2
    if pageLength[id] != 0:
        similarity = similarity / pageLength[id]
    return similarity

if __name__ == "__main__":
    pages = downloadPages('input.txt')
    trie = trie.Trie(pages)
    dictionary = trie.dictionary
    pageLength = trie.pageLength
    while True:
        search()


from flask import Blueprint, request, g, json
from models.trainings import Meeting, Training
from werkzeug.exceptions import BadRequest, NotFound
from models.db import db
from src.utils import datetime_from_json
from access_guards import only_worker
from models.trainings import Participation, Training

bp = Blueprint('meetings', __name__, url_prefix='/meetings')

@bp.route('/', methods=['POST'])
@only_worker
def create_meeting():
    request_body = request.form
    training_id = request_body.get('training_id')
    begin_date = request_body.get('begin_date')
    end_date = request_body.get('end_date')

    if not begin_date or not end_date or not training_id:
        raise BadRequest('All parameters should be not an empty string!')

    if Training.query.filter_by(id=training_id).count() == 0:
        raise BadRequest('Training with this id does not exist!')

    try:
        begin_date = datetime_from_json(begin_date)
        end_date = datetime_from_json(end_date)
    except:
        raise BadRequest('Invalid format of date. Date must be YYYY-MM-DDTHH:MM:SS.00Z')

    if begin_date > end_date:
        raise BadRequest('Begin date have to be before end date!')

    meeting = Meeting(begin_date, end_date, training_id)

    db.session.add(meeting)
    db.session.commit()

    return meeting.to_dict()

@bp.route('/<int:meeting_id>', methods=['PATCH'])
@only_worker
def edit_meeting(meeting_id):
    request_body = request.form
    meeting = Meeting.query.filter_by(id=meeting_id).first()
    begin_date = request_body.get('begin_date')
    end_date = request_body.get('end_date')

    if not meeting:
        raise NotFound('Meeting with this id does not exsist!')

    if not begin_date and not end_date:
        raise BadRequest('Nothing to edit..')

    try:
        if begin_date:
            meeting.begin_date = datetime_from_json(begin_date)

        if end_date:
            meeting.end_date = datetime_from_json(end_date)
    except:
        raise BadRequest('Invalid format of date. Date must be YYYY-MM-DDTHH:MM:SS.00Z')

    if meeting.begin_date > meeting.end_date:
        raise BadRequest('Begin date have to be before end date!')

    db.session.commit()

    return meeting.to_dict()

@bp.route('/<int:meeting_id>', methods=['DELETE'])
@only_worker
def delete_meeting(meeting_id):
    meeting = Meeting.query.filter_by(id=meeting_id).first()

    if not meeting:
        raise NotFound('Meeting with this id does not exsist!')

    if Training.query.filter_by(id=meeting.training_id, instructor_id=g.user.id).count() == 0:
        raise BadRequest('You cannot delete meeting if you are not trainer of this!')

    if Participation.query.filter_by(training_id=meeting.training_id).count() != 0:
        raise BadRequest('You can not delete meeting with trainees!')

    db.session.delete(meeting)
    db.session.commit()

    return meeting.to_dict()