import json
import os

import frappe

SALARY_COMPONENTS_JSON = os.path.join(
    os.path.dirname(os.path.dirname(__file__)),
    "default_data",
    "salary_components.json",
)

LEAVE_TYPES_JSON = os.path.join(
    os.path.dirname(os.path.dirname(__file__)),
    "default_data",
    "leave_types.json",
)

COMPANY_DEFAULTS_JSON = os.path.join(
    os.path.dirname(os.path.dirname(__file__)),
    "default_data",
    "company_defaults.json",
)


HRMS_DEFAULT_COMPONENTS = ["Basic", "Arrear", "Leave Encashment", "Income Tax"]

HRMS_DEFAULT_LEAVE_TYPES = [
    "Casual Leave", "Compensatory Off", "Sick Leave", "Privilege Leave", "Leave Without Pay"
]


def create_default_salary_components(args):
    if not os.path.exists(SALARY_COMPONENTS_JSON):
        return

    company = args.get("company_name") or frappe.db.get_single_value("Global Defaults", "default_company")

    with open(SALARY_COMPONENTS_JSON, encoding="utf-8") as f:
        components = json.load(f)

    for data in components:
        name = data.get("salary_component")
        if not name or frappe.db.exists("Salary Component", name):
            continue

        data["doctype"] = "Salary Component"

        # Резолвим account_name → полное имя счёта в целевой компании
        accounts = []
        for row in data.get("accounts") or []:
            account_name = row.get("account")
            if account_name and company:
                full_account = frappe.db.get_value(
                    "Account", {"account_name": account_name, "company": company}, "name"
                )
                if full_account:
                    accounts.append({"company": company, "account": full_account})
        data["accounts"] = accounts

        frappe.get_doc(data).insert(ignore_permissions=True)

    for name in HRMS_DEFAULT_COMPONENTS:
        if frappe.db.exists("Salary Component", name):
            frappe.delete_doc("Salary Component", name, ignore_permissions=True)

    frappe.db.commit()


SC_SYNC_FIELDS = (
    "salary_component_abbr", "type", "description",
    "depends_on_payment_days", "is_tax_applicable",
    "deduct_full_tax_on_selected_payroll_date",
    "variable_based_on_taxable_salary", "is_income_tax_component",
    "exempted_from_income_tax", "round_to_the_nearest_integer",
    "statistical_component", "accrual_component",
    "do_not_include_in_total", "do_not_include_in_accounts",
    "remove_if_zero_valued", "arrear_component", "disabled",
    "condition", "amount", "amount_based_on_formula", "formula",
    "is_flexible_benefit", "payout_method",
    "final_cycle_accrual_payout", "max_benefit_amount",
)


def sync_salary_components():
    """Полная синхронизация Salary Component с default_data/salary_components.json.
    JSON — single source of truth: создаёт отсутствующие, обновляет существующие,
    удаляет лишние (не в JSON). Если удалить нельзя из-за linked records —
    компонент помечается disabled. Идемпотентна, безопасна для повторного запуска.
    Запускается автоматически из after_migrate.
    """
    if not os.path.exists(SALARY_COMPONENTS_JSON):
        return

    with open(SALARY_COMPONENTS_JSON, encoding="utf-8") as f:
        components = json.load(f)

    companies = frappe.get_all("Company", pluck="name")

    def resolve_accounts(json_accounts):
        out = []
        for row in json_accounts or []:
            account_name = row.get("account")
            if not account_name:
                continue
            for company in companies:
                full = frappe.db.get_value(
                    "Account",
                    {"account_name": account_name, "company": company, "is_group": 0},
                    "name",
                )
                if full:
                    out.append({"company": company, "account": full})
        return out

    json_names = set()
    for data in components:
        name = data.get("salary_component")
        if not name:
            continue
        json_names.add(name)
        accounts = resolve_accounts(data.get("accounts"))

        if frappe.db.exists("Salary Component", name):
            doc = frappe.get_doc("Salary Component", name)
            for field in SC_SYNC_FIELDS:
                if field in data:
                    doc.set(field, data[field])
            doc.set("accounts", [])
            for acc in accounts:
                doc.append("accounts", acc)
            doc.save(ignore_permissions=True)
        else:
            new_data = {k: v for k, v in data.items() if k != "accounts"}
            new_data["doctype"] = "Salary Component"
            new_data["accounts"] = accounts
            frappe.get_doc(new_data).insert(ignore_permissions=True)

    # Удаляем то, чего больше нет в JSON. Если удалить нельзя — disable.
    for name in frappe.get_all("Salary Component", pluck="name"):
        if name in json_names:
            continue
        try:
            frappe.delete_doc("Salary Component", name, ignore_permissions=True)
        except Exception:
            try:
                doc = frappe.get_doc("Salary Component", name)
                doc.disabled = 1
                doc.save(ignore_permissions=True)
                frappe.log_error(
                    f"Cannot delete Salary Component '{name}' (linked records exist). "
                    f"Marked as disabled.",
                    "Salary Component Sync",
                )
            except Exception as inner:
                frappe.log_error(
                    f"Failed to disable Salary Component '{name}': {inner}",
                    "Salary Component Sync",
                )

    frappe.db.commit()


def create_default_leave_types(args):
    if not os.path.exists(LEAVE_TYPES_JSON):
        return

    with open(LEAVE_TYPES_JSON, encoding="utf-8") as f:
        leave_types = json.load(f)

    for data in leave_types:
        name = data.get("leave_type_name")
        if not name or frappe.db.exists("Leave Type", name):
            continue

        data["doctype"] = "Leave Type"
        frappe.get_doc(data).insert(ignore_permissions=True)

    for name in HRMS_DEFAULT_LEAVE_TYPES:
        if frappe.db.exists("Leave Type", name):
            frappe.delete_doc("Leave Type", name, ignore_permissions=True)

    frappe.db.commit()


LEAVE_TYPE_SYNC_FIELDS = (
    "max_leaves_allowed", "applicable_after", "max_continuous_days_allowed",
    "is_carry_forward", "is_lwp", "is_ppl",
    "fraction_of_daily_salary_per_leave", "is_optional_leave",
    "allow_negative", "allow_over_allocation", "include_holiday",
    "is_compensatory", "maximum_carry_forwarded_leaves",
    "expire_carry_forwarded_leaves_after_days",
    "allow_encashment", "max_encashable_leaves", "non_encashable_leaves",
    "earning_component", "is_earned_leave", "earned_leave_frequency",
    "allocate_on_day", "rounding",
)


def sync_leave_types():
    """Полная синхронизация Leave Type с default_data/leave_types.json.
    JSON — single source of truth. Создаёт отсутствующие, обновляет
    существующие, удаляет лишние. Если удалить нельзя из-за linked records
    (Leave Allocation/Application) — пишет в Error Log и пропускает
    (у Leave Type нет поля disabled). Идемпотентна.
    Должна запускаться ПОСЛЕ sync_salary_components, т.к. earning_component
    может ссылаться на Salary Component.
    """
    if not os.path.exists(LEAVE_TYPES_JSON):
        return

    with open(LEAVE_TYPES_JSON, encoding="utf-8") as f:
        leave_types = json.load(f)

    json_names = set()
    for data in leave_types:
        name = data.get("leave_type_name")
        if not name:
            continue
        json_names.add(name)

        if frappe.db.exists("Leave Type", name):
            doc = frappe.get_doc("Leave Type", name)
            for field in LEAVE_TYPE_SYNC_FIELDS:
                if field in data:
                    doc.set(field, data[field])
            doc.save(ignore_permissions=True)
        else:
            new_data = dict(data)
            new_data["doctype"] = "Leave Type"
            frappe.get_doc(new_data).insert(ignore_permissions=True)

    for name in frappe.get_all("Leave Type", pluck="name"):
        if name in json_names:
            continue
        try:
            frappe.delete_doc("Leave Type", name, ignore_permissions=True)
        except Exception as e:
            frappe.log_error(
                f"Cannot delete Leave Type '{name}' (likely linked records): {e}",
                "Leave Type Sync",
            )

    frappe.db.commit()


def create_default_company_accounts(args):
    if not os.path.exists(COMPANY_DEFAULTS_JSON):
        return

    company = args.get("company_name") or frappe.db.get_single_value("Global Defaults", "default_company")
    if not company:
        return

    with open(COMPANY_DEFAULTS_JSON, encoding="utf-8") as f:
        defaults = json.load(f)

    doc = frappe.get_doc("Company", company)
    updated = False

    for fieldname, account_number in defaults.items():
        full_account = frappe.db.get_value(
            "Account", {"account_number": account_number, "company": company}, "name"
        )
        if full_account:
            doc.set(fieldname, full_account)
            updated = True

    if updated:
        doc.save(ignore_permissions=True)
        frappe.db.commit()


def sync_company_defaults():
    """Синхронизирует default-счета на ВСЕХ Company с default_data/company_defaults.json.
    Для каждого поля из JSON ищет Account по account_number в каждой компании
    и проставляет полное имя счёта на Company. Поля, которых в JSON нет,
    не трогает. Если в компании нет аккаунта с таким номером — поле этой
    компании не меняется (silent skip). Идемпотентна.
    """
    if not os.path.exists(COMPANY_DEFAULTS_JSON):
        return

    with open(COMPANY_DEFAULTS_JSON, encoding="utf-8") as f:
        defaults = json.load(f)

    if not defaults:
        return

    for company in frappe.get_all("Company", pluck="name"):
        doc = frappe.get_doc("Company", company)
        updated = False
        for fieldname, account_number in defaults.items():
            full = frappe.db.get_value(
                "Account",
                {"account_number": account_number, "company": company},
                "name",
            )
            if full and doc.get(fieldname) != full:
                doc.set(fieldname, full)
                updated = True
        if updated:
            doc.save(ignore_permissions=True)

    frappe.db.commit()


def setup_wizard_complete_handler(args):
    """
    Вызывается после завершения ERPNext setup wizard.
    Ставит в очередь фоновую задачу для создания категорий активов.

    Args:
        args: Словарь с параметрами визарда, включая:
            - company_name: Название созданной компании
            - country: Выбранная страна
            - currency: Валюта по умолчанию
    """
    frappe.log_error(
        f"Hook called\nArgs: {args}",
        "AC Setup Hook"
    )

    company_name = args.get("company_name")

    if not company_name:
        # Fallback: получаем первую компанию
        companies = frappe.get_all("Company", limit=1)

        if companies:
            company_name = companies[0].name
            frappe.log_error(
                f"Company name not in args, using fallback\nCompanies found: {companies}\nUsing: {company_name}",
                "AC Setup Fallback"
            )
        else:
            frappe.log_error(
                "Company name not found in setup wizard args and no companies exist in database",
                "AC Setup ERROR"
            )
            return

    # Ставим в очередь фоновую задачу (некритично, не блокирует визард)
    try:
        frappe.enqueue(
            "jey_erp.custom.create_asset_categories.create_asset_categories_for_company",
            queue="default",
            timeout=300,
            enqueue_after_commit=True,
            at_front=False,  # Некритично, может подождать
            company_name=company_name
        )
        frappe.log_error(
            f"Background task enqueued successfully\nCompany: {company_name}\nQueue: default\nTimeout: 300s",
            "AC Task Enqueued"
        )
    except Exception as e:
        import traceback
        frappe.log_error(
            f"Failed to enqueue background task\nCompany: {company_name}\nError: {str(e)}\n\nTraceback:\n{traceback.format_exc()}",
            "AC Enqueue ERROR"
        )
        raise

    frappe.msgprint(
        f"Категории активов будут созданы в фоновом режиме для {company_name}",
        title="Настройка выполняется",
        indicator="blue"
    )
