# from django.shortcuts import render
# from django.http import HttpResponse
import math
from datetime import datetime
import random
import string

from finance_and_accounting.models import Asset
from human_resource.models import MessageStaffReadStatus, MyMessage, MyNotification, NotificationStaffReadStatus, StaffProfile
import os
import pytz

from megawatt_agents.models import AgentProfile
from procurement.models import PurchaseOrder
from system_administration.models import CompanyDepartment
from warehouse_management.models import Category, Inventory, InventoryFinancialYearOpening, Product


def send_email_signup(request, message, recipient_list):
    from system_administration.email_settings import dispatch_signup_mail
    return dispatch_signup_mail(message, recipient_list, agent=False, request=request)


def send_email_signup_agent(request, message, recipient_list):
    from system_administration.email_settings import dispatch_signup_mail
    return dispatch_signup_mail(message, recipient_list, agent=True, request=request)


def send_error_messages(request, message):
    from system_administration.email_settings import dispatch_error_notification
    return dispatch_error_notification(message, request=request)


def send_general_mail(request, subject, message, recipient_list):
    from system_administration.email_settings import dispatch_general_mail
    return dispatch_general_mail(subject, message, recipient_list, request=request)


def send_marketing_mail(request, subject, body, recipient_list, files=None):
    from system_administration.email_settings import dispatch_marketing_mail
    return dispatch_marketing_mail(
        subject,
        body,
        recipient_list,
        files=files,
        request=request,
    )


def _staff_position_title_for_map(staff):
    """Position title for nested creator/reliever maps; staff may be None or have no `staff_position`."""
    if staff is None or staff.staff_position is None:
        return ""
    return staff.staff_position.position_title


def get_staff_profile_data(userActive):
    staff_profile_data = {}
    date_format = '%d/%m/%Y, %H:%M'
    staff_profile_data["email_address"] = userActive.username
    current_date = datetime.now().date()
    target_timezone = pytz.timezone('Africa/Nairobi')
    if userActive.user_staff_profile is not None and userActive.user_staff_profile.recycle_bin == False:
        staff_profile_data["staff_id"] = str(userActive.user_staff_profile.id)
        staff_profile_data["staff_position"] = userActive.user_staff_profile.staff_position.position_title if userActive.user_staff_profile.staff_position is not None else ""
        staff_profile_data["staff_number"] = userActive.user_staff_profile.staff_number
        staff_profile_data["first_name"] = userActive.user_staff_profile.first_name
        staff_profile_data["last_name"] = userActive.user_staff_profile.last_name
        staff_profile_data["date_of_birth"] = userActive.user_staff_profile.date_of_birth if userActive.user_staff_profile.date_of_birth is not None else ''
        staff_profile_data["country_name"] = userActive.user_staff_profile.country_name
        staff_profile_data["identification_number"] = userActive.user_staff_profile.identification_number
        staff_profile_data["phone_number"] = userActive.user_staff_profile.phone_number
        staff_profile_data["staff_title"] = userActive.user_staff_profile.staff_title
        staff_profile_data["employment_start_date"] = userActive.user_staff_profile.employment_start_date if userActive.user_staff_profile.employment_start_date is not None else ''
        staff_profile_data["employment_end_date"] = userActive.user_staff_profile.employment_end_date if userActive.user_staff_profile.employment_end_date is not None else ''
        staff_profile_data["emergency_contact_phone"] = userActive.user_staff_profile.emergency_contact_phone
        staff_profile_data["company_branch_name"] = userActive.user_staff_profile.company_branch.branch_name if userActive.user_staff_profile.company_branch is not None else ""
        staff_profile_data["company_department"] = userActive.user_staff_profile.company_department.department_name if userActive.user_staff_profile.company_department is not None else ""
        staff_profile_data["company_department_id"] = str(userActive.user_staff_profile.company_department.id) if userActive.user_staff_profile.company_department is not None else ""
        staff_profile_data["company_branch_id"] = str(userActive.user_staff_profile.company_branch.id) if userActive.user_staff_profile.company_branch is not None else ""
        staff_profile_data["is_profile_set"] = "true" if userActive.user_staff_profile.is_profile_set else "false"
        staff_profile_data["is_head_of_department"] = "true" if userActive.user_staff_profile.is_head_of_department else "false"
        staff_profile_data["has_read_write_priviledges"] = "true" if userActive.user_staff_profile.has_read_write_priviledges else "false"
        staff_profile_data["is_super_admin"] = "true" if userActive.user_staff_profile.is_super_admin else "false"
        staff_profile_data["is_on_leave"] = "true" if userActive.user_staff_profile.is_on_leave else "false"
        # added info
        staff_profile_data["kra_pin"] = userActive.user_staff_profile.kra_pin
        staff_profile_data["type_of_employment"] = userActive.user_staff_profile.type_of_employment
        staff_profile_data["banking_institution_name"] = userActive.user_staff_profile.banking_institution_name
        staff_profile_data["bank_account_name"] = userActive.user_staff_profile.bank_account_name
        staff_profile_data["bank_account_number"] = userActive.user_staff_profile.bank_account_number
        # added banking institution
        staff_profile_data["bank_branch_name"] = userActive.user_staff_profile.bank_branch_name
        staff_profile_data["bank_branch_code"] = userActive.user_staff_profile.bank_branch_code
        staff_profile_data["bank_swift_code"] = userActive.user_staff_profile.bank_swift_code
        # end
        staff_profile_data["nhif_number"] = userActive.user_staff_profile.nhif_number
        staff_profile_data["nhif_additional_info"] = userActive.user_staff_profile.nhif_additional_info
        staff_profile_data["nssf_number"] = userActive.user_staff_profile.nssf_number
        staff_profile_data["nssf_additional_info"] = userActive.user_staff_profile.nssf_additional_info
        staff_profile_data["staff_additional_info"] = userActive.user_staff_profile.staff_additional_info
        staff_profile_data["basic_salary"] = userActive.user_staff_profile.basic_salary
        staff_profile_data["is_profile_active"] = "true" if userActive.user_staff_profile.is_profile_active == True else "false"
        # end
        staff_profile_data["created_on"] = (
            datetime.strftime(userActive.user_staff_profile.created_on, date_format)
            if userActive.user_staff_profile.created_on is not None
            else ""
        )
        staff_profile_data["last_updated_on"] = (
            datetime.strftime(userActive.user_staff_profile.last_updated_on, date_format)
            if userActive.user_staff_profile.last_updated_on is not None
            else ""
        )
        #
        staff_profile_data["time_sheets_list"] = []
        staff_profile_data["education_qualifications_list"] = []
        staff_profile_data["staff_training_records_list"] = []
        staff_profile_data["staff_disciplinary_records_list"] = []
        staff_leaves_list = []
        staff_leaves = userActive.user_staff_profile.staff_leave_instances.all().order_by("-id")
        current_month_number = current_date.month
        current_year = current_date.year
        valid_leave_days = (current_month_number-1)*1.75
        for staff_leave in staff_leaves:
            if staff_leave.recycle_bin == False:
                staff_leave_map = {}
                if staff_leave.leave_hr_approval == True and staff_leave.leave_department_approval == True and (staff_leave.leave_type != "sick_leave" or staff_leave.leave_type != "maternity_leave" or staff_leave.leave_type != "paternity_leave" or staff_leave.leave_type != "medical_leave" or staff_leave.leave_type != "bereavement_leave"):
                    valid_leave_days -= int(
                        staff_leave.number_of_leave_days)
                staff_leave_map["leave_id"] = str(
                    staff_leave.id)
                staff_leave_map["leave_type"] = staff_leave.leave_type
                staff_leave_map["leave_description"] = staff_leave.leave_description
                staff_leave_map["leave_start_date"] = datetime.strftime(
                    staff_leave.leave_start_date, '%d/%m/%Y') if staff_leave.leave_start_date is not None else ""
                staff_leave_map["leave_end_date"] = datetime.strftime(
                    staff_leave.leave_end_date, '%d/%m/%Y') if staff_leave.leave_end_date is not None else ""
                # only count working days
                staff_leave_map["number_of_leave_days"] = staff_leave.number_of_leave_days
                staff_leave_map["leave_department_approval"] = staff_leave.leave_department_approval
                staff_leave_map["leave_hr_approval"] = staff_leave.leave_hr_approval
                staff_leave_map["leave_status"] = staff_leave.leave_status
                creator_map = {}
                creator_map["staff_id"] = str(
                    staff_leave.created_by.id) if staff_leave.created_by is not None else ""
                creator_map["staff_name"] = f'{staff_leave.created_by.first_name} {staff_leave.created_by.last_name}' if staff_leave.created_by is not None else ""
                creator_map["staff_position"] = _staff_position_title_for_map(staff_leave.created_by)
                staff_leave_map["created_by"] = creator_map
                creator_map = {}
                creator_map["staff_id"] = str(
                    staff_leave.last_updated_by.id) if staff_leave.last_updated_by is not None else ""
                creator_map["staff_name"] = f'{staff_leave.last_updated_by.first_name} {staff_leave.last_updated_by.last_name}' if staff_leave.last_updated_by is not None else ""
                creator_map["staff_position"] = _staff_position_title_for_map(staff_leave.last_updated_by)
                staff_leave_map["last_updated_by"] = creator_map
                creator_map = {}
                #
                creator_map["staff_id"] = str(
                    staff_leave.staff_reliever.id) if staff_leave.staff_reliever is not None else ""
                creator_map["staff_name"] = f'{staff_leave.staff_reliever.first_name} {staff_leave.staff_reliever.last_name}' if staff_leave.staff_reliever is not None else ""
                creator_map["staff_position"] = _staff_position_title_for_map(staff_leave.staff_reliever)
                staff_leave_map["staff_reliever"] = creator_map
                #
                staff_leave_map["created_on"] = datetime.strftime(
                    staff_leave.created_on.astimezone(target_timezone), date_format) if staff_leave.created_on is not None else ""
                staff_leave_map["last_updated_on"] = datetime.strftime(
                    staff_leave.last_updated_on.astimezone(target_timezone), date_format) if staff_leave.last_updated_on is not None else ""
                # print(staff_leave_map)
                staff_leaves_list.append(staff_leave_map)
        staff_profile_data["staff_leaves_list"] = staff_leaves_list
        staff_profile_data["available_leave_days"] = str(
            valid_leave_days) if valid_leave_days > 0 else "0"
        staff_profile_data["staff_bonus_schemes_list"] = []
        staff_profile_data["staff_deduction_schemes_list"] = []
        # added
        staff_profile_data["personal_email"] = userActive.user_staff_profile.personal_email
    else:
        staff_profile_data["email_address"] = ""
        staff_profile_data["staff_id"] = ""
        staff_profile_data["staff_position"] = ""
        staff_profile_data["staff_number"] = ""
        staff_profile_data["first_name"] = ""
        staff_profile_data["last_name"] = ""
        staff_profile_data["date_of_birth"] = ""
        staff_profile_data["country_name"] = ""
        staff_profile_data["identification_number"] = ""
        staff_profile_data["phone_number"] = ""
        staff_profile_data["staff_title"] = ""
        staff_profile_data["employment_start_date"] = ""
        staff_profile_data["employment_end_date"] = ""
        staff_profile_data["emergency_contact_phone"] = ""
        staff_profile_data["is_profile_set"] = ""
        staff_profile_data["is_head_of_department"] = ""
        staff_profile_data["has_read_write_priviledges"] = ""
        staff_profile_data["is_super_admin"] = ""
        staff_profile_data["is_on_leave"] = ""
        staff_profile_data["company_branch_name"] = ""
        staff_profile_data["company_department"] = ""
        staff_profile_data["created_on"] = ""
        staff_profile_data["last_updated_on"] = ""
        staff_profile_data["kra_pin"] = ""
        staff_profile_data["type_of_employment"] = ""
        staff_profile_data["userActive.user_staff_profile"] = ""
        staff_profile_data["bank_account_name"] = ""
        staff_profile_data["bank_account_number"] = ""
        staff_profile_data["bank_branch_name"] = ""
        staff_profile_data["bank_branch_code"] = ""
        staff_profile_data["bank_swift_code"] = ""
        staff_profile_data["nhif_number"] = ""
        staff_profile_data["nhif_additional_info"] = ""
        staff_profile_data["nssf_number"] = ""
        staff_profile_data["nssf_additional_info"] = ""
        staff_profile_data["staff_additional_info"] = ""
        staff_profile_data["basic_salary"] = ""
        staff_profile_data["time_sheets_list"] = []
        staff_profile_data["education_qualifications_list"] = []
        staff_profile_data["staff_training_records_list"] = []
        staff_profile_data["staff_disciplinary_records_list"] = []
        staff_profile_data["staff_leaves_list"] = []
        staff_profile_data["staff_bonus_schemes_list"] = []
        staff_profile_data["staff_deduction_schemes_list"] = []
        staff_profile_data["personal_email"] = ""
        staff_profile_data["is_profile_active"] = ""
        staff_profile_data["available_leave_days"] = ""
    return staff_profile_data


def get_staff_finance_data(staff_id):
    staff_map = {}
    staff_deduction_schemes_list = []
    staff_bonus_schemes_list = []
    date_format = '%d/%m/%Y, %H:%M'
    current_date = datetime.now().date()
    staff = StaffProfile.objects.get(id=staff_id)
    staff_map["staff_id"] = str(staff.id)
    staff_map["email_address"] = staff.user.username
    staff_map["staff_position_id"] = str(
        staff.staff_position.id) if staff.staff_position is not None else ""
    staff_map["staff_position"] = staff.staff_position.position_title if staff.staff_position is not None else ""
    staff_map["staff_number"] = staff.staff_number
    staff_map["first_name"] = staff.first_name
    staff_map["last_name"] = staff.last_name
    staff_map["date_of_birth"] = staff.date_of_birth if staff.date_of_birth is not None else ''
    staff_map["country_name"] = staff.country_name
    staff_map["identification_number"] = staff.identification_number
    staff_map["phone_number"] = staff.phone_number
    staff_map["staff_title"] = staff.staff_title
    staff_map["employment_start_date"] = staff.employment_start_date if staff.employment_start_date is not None else ''
    staff_map["employment_end_date"] = staff.employment_end_date if staff.employment_end_date is not None else ''
    staff_map["kra_pin"] = staff.kra_pin
    staff_map["type_of_employment"] = staff.type_of_employment
    staff_map["banking_institution_name"] = staff.banking_institution_name
    staff_map["bank_account_name"] = staff.bank_account_name
    staff_map["bank_account_number"] = staff.bank_account_number
    # added bank details
    staff_map["bank_branch_name"] = staff.bank_branch_name
    staff_map["bank_branch_code"] = staff.bank_branch_code
    staff_map["bank_swift_code"] = staff.bank_swift_code
    # end
    staff_map["nhif_number"] = staff.nhif_number
    staff_map["sha_number"] = staff.nhif_number
    staff_map["nhif_additional_info"] = staff.nhif_additional_info
    staff_map["nssf_number"] = staff.nssf_number
    staff_map["basic_salary"] = staff.basic_salary
    staff_map["company_department"] = staff.company_department.department_name
    staff_map["company_branch_name"] = staff.company_branch.branch_name
    staff_map["is_profile_active"] = "true" if staff.is_profile_active == True else "false"
    ###
    staff_deduction_schemes = staff.staff_deduction_schemes.select_related("deduction").all().order_by('-id')
    for staff_deduction_scheme in staff_deduction_schemes:
        from human_resource.staff_deduction_utils import staff_deduction_scheme_map
        scheme_map = staff_deduction_scheme_map(staff_deduction_scheme, current_date)
        if scheme_map:
            staff_deduction_schemes_list.append(scheme_map)
    staff_map["staff_deduction_schemes_list"] = staff_deduction_schemes_list
    staff_bonus_schemes = staff.staff_bonus_schemes.all().order_by('-id')
    for staff_bonus_scheme in staff_bonus_schemes:
        staff_bonus_scheme_map = {}
        staff_bonus_scheme_map["staff_bonus_scheme_id"] = str(
            staff_bonus_scheme.id)
        staff_bonus_scheme_map["bonus_id"] = str(
            staff_bonus_scheme.bonus.id)
        staff_bonus_scheme_map["bonus_title"] = staff_bonus_scheme.bonus.bonus_title
        staff_bonus_scheme_map["bonus_description"] = staff_bonus_scheme.bonus.bonus_description
        staff_bonus_scheme_map["bonus_type"] = staff_bonus_scheme.bonus.bonus_type
        staff_bonus_scheme_map["bonus_amount"] = staff_bonus_scheme.bonus.bonus_amount
        if staff_bonus_scheme.bonus.date_effective_to >= current_date:
            staff_bonus_schemes_list.append(
                staff_bonus_scheme_map)
    staff_map["staff_bonus_schemes_list"] = staff_bonus_schemes_list
    ###
    try:
        from finance_and_accounting.employee_advance import get_staff_advance_payload
        staff_map.update(get_staff_advance_payload(staff_id))
    except Exception:
        staff_map["employee_advance_balance"] = "0.00"
        staff_map["employee_advance_deduction_id"] = ""
        staff_map["employee_advances_list"] = []

    try:
        from human_resource.payroll_calculations import build_payroll_preview_from_staff_data
        company_profile = (
            staff.company_branch.company_profile
            if staff.company_branch is not None
            else None
        )
        preview = build_payroll_preview_from_staff_data(
            staff_map,
            company_profile=company_profile,
        )
        if preview.get("payroll_preview_available") == "true":
            staff_map.update(preview)
    except Exception:
        staff_map["payroll_preview_available"] = "false"

    return staff_map


def get_available_leave_days(staff_id):
    current_date = datetime.now().date()
    current_year = current_date.year
    current_month_number = current_date.month
    valid_leave_days = (current_month_number-1)*1.75
    staff = StaffProfile.objects.get(id=staff_id)
    staff_leaves = staff.staff_leave_instances.filter(recycle_bin=False)
    for staff_leave in staff_leaves:
        for staff_leave in staff_leaves:
            # compute available leave days
            if staff_leave.leave_hr_approval == True and staff_leave.leave_start_date.year == current_year and staff_leave.leave_department_approval == True and (staff_leave.leave_type != "sick_leave" or staff_leave.leave_type != "maternity_leave" or staff_leave.leave_type != "paternity_leave" or staff_leave.leave_type != "medical_leave" or staff_leave.leave_type != "bereavement_leave"):
                valid_leave_days -= int(
                    staff_leave.number_of_leave_days)
    return valid_leave_days


def generate_random_string(length):
    alphanumeric_chars = string.ascii_letters + string.digits
    return ''.join(random.choice(alphanumeric_chars) for _ in range(length))


def checkIfStaffCanEdit(staffProfile, editedStaffDepId):
    """
    Who may edit another staff profile via `edit_staff_profile` (non–self-edit).

    - System administrator / company super-admin: always (see `check_if_system_administrator`).
    - Head of Human Resource Management with read/write: any staff in the company (same as
      practical Flutter / ERP expectation for privilege management).
    - Otherwise: head or read/write in the **same department** as the profile being saved
      (`editedStaffDepId` is the target `company_department_id` from the form).
    """
    if check_if_system_administrator(staffProfile):
        return True
    dept = getattr(staffProfile, "company_department", None)
    if (
        dept is not None
        and getattr(dept, "department_name", None) == "human_resource_management"
        and staffProfile.is_head_of_department is True
        and staffProfile.has_read_write_priviledges is True
    ):
        return True
    if dept is None:
        return False
    if (
        (staffProfile.is_head_of_department is True or staffProfile.has_read_write_priviledges is True)
        and str(staffProfile.company_department.id) == str(editedStaffDepId)
    ):
        return True
    return False


def convertTo24HRFormat(time_string):
    time_object_12hr = datetime.strptime(time_string, '%I:%M %p').time()
    hour_24hr = time_object_12hr.hour if time_object_12hr.hour != 12 else 0
    minute = time_object_12hr.minute
    time_object_24hr = datetime.strptime(
        f'{hour_24hr:02d}:{minute:02d}', '%H:%M').time()
    return time_object_24hr

# to be added


def generateNextStaffNumber(company_id):
    all_staff = StaffProfile.objects.all()
    largest_id = 0
    staff_to_use = None
    newNumber = 'MEL000'
    superHrNumber = ''
    for staff in all_staff:
        if staff.company_branch.company_profile.id == company_id:
            if staff.id > largest_id and staff.is_super_admin != True:
                largest_id = staff.id
                staff_to_use = staff
        if staff.is_super_admin:
            superHrNumber = staff.staff_number
            superHrNumber = superHrNumber[3:]
    if staff_to_use is not None:
        staff_number = staff_to_use.staff_number
        stripped_string = staff_number[3:]
        # print(stripped_string)
        newSuffix = int(stripped_string) + 1
        if int(superHrNumber) == newSuffix:
            newSuffix += 1
        if newSuffix < 10:
            newNumber = f'MEL00{newSuffix}'
        elif newSuffix < 100:
            newNumber = f'MEL0{newSuffix}'
        else:
            newNumber = f'MEL{newSuffix}'
    return newNumber


def create_notifications(notification_title, notification_body, list_of_staff_ids):
    new_notification = MyNotification.objects.create(
        notification_title=notification_title, notification_body=notification_body)
    for staff_id in list_of_staff_ids:
        staff_instance = StaffProfile.objects.get(id=staff_id)
        NotificationStaffReadStatus.objects.create(
            staff=staff_instance, notification=new_notification)


def create_messages(message_title, message_body, list_of_staff_ids):
    new_message = MyMessage.objects.create(
        message_title=message_title, message_body=message_body)
    for staff_id in list_of_staff_ids:
        staff_instance = StaffProfile.objects.get(id=staff_id)
        MessageStaffReadStatus.objects.create(
            staff=staff_instance, message=new_message)


def send_email_with_attachment(request, subject, body, to_email, to_personal_email, file_name, uploaded_pdf_content):
    from system_administration.email_settings import dispatch_attachment_mail
    return dispatch_attachment_mail(
        subject,
        body,
        [to_email, to_personal_email],
        file_name,
        uploaded_pdf_content,
        request=request,
    )


def create_folder_if_not_exists():
    folder_name = "megawatt_documents"
    # Base directory of the project
    base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    folder_path = os.path.join(base_dir, folder_name)
    if not os.path.exists(folder_path):
        os.makedirs(folder_path)
    return folder_path


def get_hod_technical_sales_ids():
    company_department = CompanyDepartment.objects.get(
        department_name="sales")
    technical_sales_hod_staff = StaffProfile.objects.filter(
        recycle_bin=False, company_department=company_department.id, is_head_of_department=True)
    list_of_ids = []
    for staff in technical_sales_hod_staff:
        list_of_ids.append(staff.id)
    return list_of_ids


def get_price_per_unit_for_customer_type(product_id, customer_profile):
    price_per_unit = "0.00"
    product_instance = Product.objects.get(id=int(product_id))
    product_pricing = product_instance.product_pricing
    if customer_profile.customer_type == "engineering_procurement_contractor":
        if float(product_pricing.product_net_price_engineering_procurement_contractor) > 0:
            price_per_unit = product_pricing.product_net_price_engineering_procurement_contractor
        else:
            price_per_unit = product_pricing.product_net_price
    if customer_profile.customer_type == "technician":
        if float(product_pricing.product_net_price_technician) > 0:
            price_per_unit = product_pricing.product_net_price_technician
        else:
            price_per_unit = product_pricing.product_net_price
    if customer_profile.customer_type == "reseller":
        if float(product_pricing.product_net_price_reseller) > 0:
            price_per_unit = product_pricing.product_net_price_reseller
        else:
            price_per_unit = product_pricing.product_net_price
    if customer_profile.customer_type == "financier":
        if float(product_pricing.product_net_price_financier) > 0:
            price_per_unit = product_pricing.product_net_price_financier
        else:
            price_per_unit = product_pricing.product_net_price
    return price_per_unit


def get_product_discount_per_unit(product_id, price_per_unit):
    discount_per_unit = "0.00"
    product_instance = Product.objects.get(id=int(product_id))
    product_discount = product_instance.product_discount
    if product_discount.discount_type == "percentage_discount":
        discount_per_unit = str((
            float(product_discount.discount_value)/100)*float(price_per_unit))
    else:
        discount_per_unit = product_discount.discount_value
    return discount_per_unit


def get_product_vat_per_unit(product_id, price_per_unit):
    vat_per_unit = "0.00"
    product_instance = Product.objects.get(id=int(product_id))
    product_vat = product_instance.product_vat.vat_percentage_value
    vat_per_unit = str((float(product_vat)/100)*float(price_per_unit))
    return vat_per_unit


def set_initial_opening_values(initial_year, branch_instance):
    """Bulk-insert InventoryFinancialYearOpening rows for *initial_year* (opening_quantity=0).
    Uses 2 SQL queries regardless of inventory count (was N+1 before).
    """
    year_int = int(initial_year)
    all_inventories = Inventory.objects.filter(
        warehouse__company_branch=branch_instance,
        warehouse__recycle_bin=False,
        recycle_bin=False,
    )
    existing_ids = set(
        InventoryFinancialYearOpening.objects.filter(
            inventory__in=all_inventories,
            financial_year=year_int,
        ).values_list("inventory_id", flat=True)
    )
    new_records = [
        InventoryFinancialYearOpening(
            inventory=inv, financial_year=year_int, opening_quantity="0"
        )
        for inv in all_inventories
        if inv.pk not in existing_ids
    ]
    if new_records:
        InventoryFinancialYearOpening.objects.bulk_create(new_records, ignore_conflicts=True)


def set_current_opening_values(current_year, branch_instance):
    """Bulk-insert InventoryFinancialYearOpening rows for *current_year* (opening_quantity=current qty).
    Uses 2 SQL queries regardless of inventory count (was N+1 before).
    """
    year_int = int(current_year)
    all_inventories = list(Inventory.objects.filter(
        warehouse__company_branch=branch_instance,
        warehouse__recycle_bin=False,
        recycle_bin=False,
    ))
    existing_ids = set(
        InventoryFinancialYearOpening.objects.filter(
            inventory__in=all_inventories,
            financial_year=year_int,
        ).values_list("inventory_id", flat=True)
    )
    new_records = [
        InventoryFinancialYearOpening(
            inventory=inv, financial_year=year_int, opening_quantity=inv.quantity
        )
        for inv in all_inventories
        if inv.pk not in existing_ids
    ]
    if new_records:
        InventoryFinancialYearOpening.objects.bulk_create(new_records, ignore_conflicts=True)


# the value of the year is the previous year+1
def get_product_opening_inventory_quantities_overall(product_id, financial_year):
    all_inventory_financial_year = InventoryFinancialYearOpening.objects.filter(
        financial_year=str(financial_year))
    opening_quantity_total = 0
    for inventory in all_inventory_financial_year:
        if inventory.product.id == product_id:
            opening_quantity_total += float(
                inventory.opening_quantity.replace(",", ""))
    return opening_quantity_total


# the value of the year is the current year + 1
def get_product_closing_inventory_quantities_overall(product_id, financial_year):
    all_inventory_financial_year = None
    all_inventory_financial_year = InventoryFinancialYearOpening.objects.filter(
        financial_year=str(financial_year))
    opening_quantity_total = 0
    if all_inventory_financial_year:
        for inventory in all_inventory_financial_year:
            if inventory.product.id == product_id:
                opening_quantity_total += float(
                    inventory.opening_quantity.replace(",", ""))
    else:
        all_current_inventory = Inventory.objects.filter(recycle_bin=False)
        for inventory in all_current_inventory:
            if inventory.product.id == product_id:
                opening_quantity_total += float(
                    inventory.quantity.replace(",", ""))
    return opening_quantity_total


# the value of the financial year is the previous year's value
def calculate_product_cost_of_opening_inventory(financial_year,):
    finacial_year_purchase_orders = PurchaseOrder.objects.filter(
        purchase_order_approved=True, recycle_bin=False, created_on__year=financial_year)
    opening_inventory_cost = 0.00
    for purchase_order in finacial_year_purchase_orders:
        purchase_order_product_instances = purchase_order.purchase_order_product_instances.filter(
            recycle_bin=False)
        for purchase_product in purchase_order_product_instances:
            purchase_value_per_unit = float(purchase_product.purchase_value_per_unit.replace(
                ",", ""))*float(purchase_product.exchange_rate.replace(",", ""))
            # determine total landed_cost per unit on product
            total_landed_cost_per_unit = 0.0
            landed_cost_product_purchase_instances = purchase_product.landed_cost_product_purchase_instances.all()
            for landed_cost_instance in landed_cost_product_purchase_instances:
                total_landed_cost_per_unit += ((float(
                    landed_cost_instance.cost_value.replace(",", ""))*float(landed_cost_instance.exchange_rate.replace(",", "")))/landed_cost_instance.product_purchase_instance.count()/float(purchase_product.quantity_purchased.replace(",", "")))
            opening_inventory_cost += (purchase_value_per_unit +
                                       total_landed_cost_per_unit)*get_product_opening_inventory_quantities_overall(purchase_product.purchase_product.id, financial_year+1)
    return opening_inventory_cost


def calculate_purchase_order_value_for_the_year(financial_year):
    finacial_year_purchase_orders = PurchaseOrder.objects.filter(
        purchase_order_approved=True, recycle_bin=False, created_on__year=financial_year)
    purchase_value_overall = 0.00
    for purchase_order in finacial_year_purchase_orders:
        purchase_value_overall += float(
            purchase_order.purchase_value_overall.replace(",", ""))
    return purchase_value_overall


def calculate_closing_inventory_value(financial_year):
    finacial_year_purchase_orders = PurchaseOrder.objects.filter(
        purchase_order_approved=True, recycle_bin=False, created_on__year=financial_year)
    opening_inventory_cost = 0.00
    for purchase_order in finacial_year_purchase_orders:
        purchase_order_product_instances = purchase_order.purchase_order_product_instances.filter(
            recycle_bin=False)
        for purchase_product in purchase_order_product_instances:
            purchase_value_per_unit = float(purchase_product.purchase_value_per_unit.replace(
                ",", ""))*float(purchase_product.exchange_rate.replace(",", ""))
            # determine total landed_cost per unit on product
            total_landed_cost_per_unit = 0.0
            landed_cost_product_purchase_instances = purchase_product.landed_cost_product_purchase_instances.all()
            for landed_cost_instance in landed_cost_product_purchase_instances:
                total_landed_cost_per_unit += ((float(
                    landed_cost_instance.cost_value.replace(",", ""))*float(landed_cost_instance.exchange_rate.replace(",", "")))/landed_cost_instance.product_purchase_instance.count()/float(purchase_product.quantity_purchased.replace(",", "")))
            opening_inventory_cost += (purchase_value_per_unit +
                                       total_landed_cost_per_unit)*get_product_closing_inventory_quantities_overall(purchase_product.purchase_product.id, financial_year+1)
    return opening_inventory_cost


def calculate_asset_depreciation(financial_year):
    all_assets = Asset.objects.filter(recycle_bin=False)
    depreciation_cost = 0.0
    for asset in all_assets:
        if asset.status == "active" and asset.purchase_date.year < financial_year:
            if asset.depreciation_method == "straight_line":
                depreciation_per_year = (float(asset.purchase_cost.replace(",", ""))-float(
                    asset.residual_value.replace(",", "")))/float(asset.useful_life_years.replace(",", ""))
                if depreciation_per_year > 0:
                    depreciation_cost += depreciation_per_year
            if asset.depreciation_method == "declining_balance":
                depreciation_per_year = float(asset.purchase_cost.replace(",", "")) * (math.pow((
                    1-float(asset.depreciation_rate.replace(",", ""))/100), (financial_year+1 - (asset.purchase_date.year))) - math.pow((
                        1-float(asset.depreciation_rate.replace(",", ""))/100), (financial_year - asset.purchase_date.year)))
                if depreciation_per_year > 0:
                    depreciation_cost += depreciation_per_year
    return depreciation_cost


def calculate_single_asset_value_current(financial_year, asset):
    depreciation_per_year = 0.0
    asset_value_current = 0.0
    if asset.depreciation_method == "straight_line":
        depreciation_per_year = (float(asset.purchase_cost.replace(",", ""))-float(
            asset.residual_value.replace(",", "")))/float(asset.useful_life_years.replace(",", ""))
        asset_value_current = (
            (financial_year - asset.purchase_date.year)*depreciation_per_year)
        # if depreciation_per_year > 0:
        #     depreciation_cost += depreciation_per_year
    if asset.depreciation_method == "declining_balance":
        asset_value_current = float(asset.purchase_cost.replace(",", "")) * (math.pow((
            1-float(asset.depreciation_rate.replace(",", ""))/100), (financial_year - (asset.purchase_date.year))))
    return asset_value_current


def check_if_system_administrator(staff_profile):
    """
    True for system-administration department staff or company super-admins
    (is_super_admin), so they can use all ERP API modules regardless of home department.
    """
    if getattr(staff_profile, "is_super_admin", False):
        return True
    dept = getattr(staff_profile, "company_department", None)
    if dept is not None and getattr(dept, "department_name", None) == "system_and_administration":
        return True
    return False


def generateNextAgentNumber():
    all_agent = AgentProfile.objects.all()
    largest_id = 0
    agent_to_use = None
    newNumber = 'AGN001'
    # superHrNumber = ''
    for agent in all_agent:
        # if staff.company_branch.company_profile.id == company_id:
        if agent.id > largest_id:
            largest_id = agent.id
            agent_to_use = agent
        # if staff.is_super_admin:
        #     superHrNumber = staff.staff_number
        #     superHrNumber = superHrNumber[3:]
    if agent_to_use is not None:
        agent_number = agent_to_use.agent_number
        stripped_string = agent_number[3:]
        # print(stripped_string)
        newSuffix = int(stripped_string) + 1
        # if int(superHrNumber) == newSuffix:
        #     newSuffix += 1
        if newSuffix < 10:
            newNumber = f'AGN00{newSuffix}'
        elif newSuffix < 100:
            newNumber = f'AGN0{newSuffix}'
        else:
            newNumber = f'AGN{newSuffix}'
    return newNumber


def get_empty_products_for_new_categories():
    product_map = {}
    product_list = []
    all_categories = Category.objects.filter(
        recycle_bin=False).order_by("-id")
    for category in all_categories:
        if category.category_products.count() == 0:
            product_map = {}
            product_map["inventory_id"] = ""
            product_map["quantity"] = "0"
            product_map["minimum_stock_level"] = "0"
            product_map["inventory_description"] = ""
            product_image_catalogues_list = []
            product_map["product_id"] = ""
            product_map["category_id"] = str(
                category.id)
            product_map["category_name"] = category.category_name
            product_map["product_name"] = ""
            product_map["stock_keeping_unit"] = ""
            product_map["unit_of_measurement"] = ""
            product_map["product_description"] = ""

            product_map["product_image_catalogues_list"] = []
            product_list.append(product_map)
    return product_list
