import dayjs from "dayjs"

export const EXPENSE_BOOKING_MODE_ITEMS = [
    { label: "Sofortaufwand", value: "expense" },
    { label: "Abschreibung einzeln", value: "depreciation_single" },
    { label: "Abschreibung Sammelposten", value: "depreciation_bundle" },
]

export const DEPRECIATION_METHOD_ITEMS = [
    { label: "Linear", value: "linear" },
    { label: "Degressiv", value: "degressive" },
]

export const normalizeExpenseBookingMode = (value?: string | null) => {
    if (value === "depreciation_single" || value === "depreciation_bundle") return value
    return "expense"
}

export const isDepreciationBookingMode = (value?: string | null) => {
    return normalizeExpenseBookingMode(value) !== "expense"
}

export const normalizeDepreciationMethod = (value?: string | null) => {
    return value === "degressive" ? "degressive" : "linear"
}

export const createIncomingInvoiceAccount = (overrides: Record<string, any> = {}) => ({
    account: null,
    amountNet: null,
    amountTax: null,
    taxType: "19",
    costCentre: null,
    amountGross: null,
    description: "",
    bookingMode: "expense",
    depreciationMonths: null,
    depreciationStartDate: null,
    depreciationMethod: "linear",
    depreciationLabel: "",
    depreciationGroup: "",
    residualValue: 0,
    ...overrides,
})

export const normalizeIncomingInvoiceAccount = (account: Record<string, any> = {}, fallbackDate?: any) => {
    const bookingMode = normalizeExpenseBookingMode(account?.bookingMode)

    return {
        ...createIncomingInvoiceAccount(account),
        bookingMode,
        depreciationMonths: account?.depreciationMonths ? Number(account.depreciationMonths) : null,
        depreciationStartDate: account?.depreciationStartDate || fallbackDate || null,
        depreciationMethod: normalizeDepreciationMethod(account?.depreciationMethod),
        depreciationLabel: String(account?.depreciationLabel || account?.description || "").trim(),
        depreciationGroup: String(account?.depreciationGroup || "").trim(),
        residualValue: Number(account?.residualValue || 0),
    }
}

export const normalizeIncomingInvoiceAccounts = (accounts: any[] = [], fallbackDate?: any) => {
    const normalized = (accounts || []).map((account) => normalizeIncomingInvoiceAccount(account, fallbackDate))
    return normalized.length > 0 ? normalized : [createIncomingInvoiceAccount({ depreciationStartDate: fallbackDate || null })]
}

export const ensureDepreciationDefaults = (item: Record<string, any>, fallbackDate?: any) => {
    item.bookingMode = normalizeExpenseBookingMode(item?.bookingMode)

    if (!isDepreciationBookingMode(item.bookingMode)) {
        item.depreciationMonths = null
        item.depreciationStartDate = null
        item.depreciationMethod = "linear"
        item.depreciationGroup = ""
        item.residualValue = 0
        if (!item.depreciationLabel) item.depreciationLabel = item.description || ""
        return item
    }

    if (!Number(item.depreciationMonths)) item.depreciationMonths = 36
    if (!item.depreciationStartDate) item.depreciationStartDate = fallbackDate || null
    item.depreciationMethod = normalizeDepreciationMethod(item.depreciationMethod)
    if (!item.depreciationLabel) item.depreciationLabel = item.description || ""
    if (item.bookingMode !== "depreciation_bundle") item.depreciationGroup = ""
    item.residualValue = Number(item.residualValue || 0)
    return item
}

const distributeLinearDepreciation = (amount: number, months: number) => {
    const totalCents = Math.round(Number(amount || 0) * 100)
    const totalMonths = Math.max(1, Math.round(Number(months || 0)))
    const base = Math.trunc(totalCents / totalMonths)
    const remainder = totalCents - (base * totalMonths)

    return Array.from({ length: totalMonths }, (_, index) => {
        return base + (index < remainder ? 1 : 0)
    })
}

const distributeDegressiveDepreciation = (amount: number, months: number, residualValue: number) => {
    const startCents = Math.round(Math.max(0, Number(amount || 0)) * 100)
    const residualCents = Math.round(Math.max(0, Number(residualValue || 0)) * 100)
    const totalMonths = Math.max(1, Math.round(Number(months || 0)))
    const targetResidual = Math.min(startCents, residualCents)
    const depreciable = Math.max(0, startCents - targetResidual)

    if (!depreciable) return Array.from({ length: totalMonths }, () => 0)

    // Monthly degressive rate derived from start and target residual over the selected duration.
    const rate = targetResidual > 0
        ? 1 - Math.pow(targetResidual / startCents, 1 / totalMonths)
        : 1 - Math.pow(0.01 / Math.max(startCents, 1), 1 / totalMonths)

    const values: number[] = []
    let currentBookValue = startCents
    let depreciated = 0

    for (let index = 0; index < totalMonths; index += 1) {
        const remainingDepreciable = Math.max(0, depreciable - depreciated)
        if (remainingDepreciable <= 0) {
            values.push(0)
            continue
        }

        if (index === totalMonths - 1) {
            values.push(remainingDepreciable)
            depreciated += remainingDepreciable
            currentBookValue = targetResidual
            continue
        }

        const raw = Math.round(currentBookValue * rate)
        const maxAllowed = Math.max(1, remainingDepreciable - Math.max(0, totalMonths - index - 1))
        const monthValue = Math.max(1, Math.min(raw || 1, maxAllowed))
        values.push(monthValue)
        depreciated += monthValue
        currentBookValue = Math.max(targetResidual, currentBookValue - monthValue)
    }

    return values
}

export const getDepreciationPlan = ({
    amount,
    months,
    startDate,
    method,
    residualValue,
}: {
    amount: number
    months: number
    startDate: any
    method?: string
    residualValue?: number
}) => {
    const start = dayjs(startDate).startOf("month")
    const totalAmount = Number(amount || 0)
    const totalMonths = Math.max(1, Math.round(Number(months || 0)))
    const normalizedMethod = normalizeDepreciationMethod(method)
    const normalizedResidual = Number(residualValue || 0)

    if (!start.isValid() || totalAmount <= 0) return []

    const distributed = normalizedMethod === "degressive"
        ? distributeDegressiveDepreciation(totalAmount, totalMonths, normalizedResidual)
        : distributeLinearDepreciation(Math.max(0, totalAmount - normalizedResidual), totalMonths)

    return distributed.map((cents, index) => ({
        index,
        date: start.add(index, "month"),
        amount: Number((cents / 100).toFixed(2)),
    }))
}

export const getLinearDepreciationAmountForRange = ({
    amount,
    months,
    startDate,
    rangeStart,
    rangeEnd,
    method,
    residualValue,
}: {
    amount: number
    months: number
    startDate: any
    rangeStart: any
    rangeEnd: any
    method?: string
    residualValue?: number
}) => {
    const start = dayjs(startDate).startOf("month")
    const periodStart = dayjs(rangeStart).startOf("month")
    const periodEnd = dayjs(rangeEnd).endOf("month")

    if (!start.isValid() || !periodStart.isValid() || !periodEnd.isValid()) return 0

    const distributed = getDepreciationPlan({
        amount,
        months,
        startDate,
        method,
        residualValue,
    })
    let resultCents = 0

    distributed.forEach((value) => {
        const monthDate = dayjs(value.date)
        if ((monthDate.isAfter(periodStart) || monthDate.isSame(periodStart, "month"))
            && (monthDate.isBefore(periodEnd) || monthDate.isSame(periodEnd, "month"))) {
            resultCents += Math.round(Number(value.amount || 0) * 100)
        }
    })

    return Number((resultCents / 100).toFixed(2))
}

export const getIncomingInvoiceImmediateExpenseNet = (invoice: any) => {
    return Number(((invoice?.accounts || []).reduce((sum: number, account: any) => {
        const normalized = normalizeIncomingInvoiceAccount(account, invoice?.date)
        if (isDepreciationBookingMode(normalized.bookingMode)) return sum
        return sum + Number(normalized.amountNet || 0)
    }, 0)).toFixed(2))
}

export const getIncomingInvoiceImmediateExpenseGross = (invoice: any) => {
    return Number(((invoice?.accounts || []).reduce((sum: number, account: any) => {
        const normalized = normalizeIncomingInvoiceAccount(account, invoice?.date)
        if (isDepreciationBookingMode(normalized.bookingMode)) return sum

        const amountGross = Number(normalized.amountGross)
        return sum + (Number.isFinite(amountGross) ? amountGross : Number(normalized.amountNet || 0) + Number(normalized.amountTax || 0))
    }, 0)).toFixed(2))
}

export const getIncomingInvoiceDepreciationRows = (invoice: any, rangeStart: any, rangeEnd: any) => {
    return (invoice?.accounts || [])
        .map((account: any, index: number) => {
            const normalized = ensureDepreciationDefaults(
                normalizeIncomingInvoiceAccount(account, invoice?.date),
                invoice?.date
            )

            if (!isDepreciationBookingMode(normalized.bookingMode)) return null

            const amount = Number(normalized.amountNet || 0)
            const months = Number(normalized.depreciationMonths || 0)
            const plan = getDepreciationPlan({
                amount,
                months,
                startDate: normalized.depreciationStartDate || invoice?.date,
                method: normalized.depreciationMethod,
                residualValue: normalized.residualValue,
            })
            const periodAmount = Number(plan.reduce((sum, item) => {
                const monthDate = dayjs(item.date)
                const periodStart = dayjs(rangeStart).startOf("month")
                const periodEnd = dayjs(rangeEnd).endOf("month")
                if ((monthDate.isAfter(periodStart) || monthDate.isSame(periodStart, "month"))
                    && (monthDate.isBefore(periodEnd) || monthDate.isSame(periodEnd, "month"))) {
                    return sum + Number(item.amount || 0)
                }
                return sum
            }, 0).toFixed(2))

            if (!periodAmount) return null

            const alreadyDepreciated = Number(plan
                .filter((entry) => dayjs(entry.date).isBefore(dayjs().startOf("month")) || dayjs(entry.date).isSame(dayjs().startOf("month"), "month"))
                .reduce((sum, entry) => sum + Number(entry.amount || 0), 0)
                .toFixed(2))
            const residualBookValue = Number(Math.max(0, amount - alreadyDepreciated).toFixed(2))

            return {
                source: "incominginvoice",
                sourceId: invoice?.id,
                index,
                mode: normalized.bookingMode,
                label: normalized.depreciationLabel || normalized.description || invoice?.reference || `Eingangsbeleg ${invoice?.id}`,
                group: normalized.depreciationGroup || null,
                amount: periodAmount,
                months,
                startDate: normalized.depreciationStartDate || invoice?.date,
                method: normalized.depreciationMethod,
                residualValue: Number(normalized.residualValue || 0),
                alreadyDepreciated,
                residualBookValue,
                vendor: invoice?.vendor,
                reference: invoice?.reference,
            }
        })
        .filter(Boolean)
}

export const getStatementAllocationImmediateExpenseAmount = (allocation: any) => {
    const hasLinkedDocument = allocation?.incominginvoice || allocation?.createddocument || allocation?.ii_id || allocation?.cd_id
    if (hasLinkedDocument) return 0

    const mode = normalizeExpenseBookingMode(allocation?.bookingMode)
    const amount = Number(allocation?.amount || 0)
    if (mode !== "expense" || amount >= 0) return 0
    return Number(Math.abs(amount).toFixed(2))
}

export const getStatementAllocationDepreciationAmount = (allocation: any, rangeStart: any, rangeEnd: any) => {
    const mode = normalizeExpenseBookingMode(allocation?.bookingMode)
    const rawAmount = Number(allocation?.amount || 0)
    const amount = Math.abs(rawAmount)
    if (mode === "expense" || rawAmount >= 0 || amount <= 0) return 0

    return getLinearDepreciationAmountForRange({
        amount,
        months: Number(allocation?.depreciationMonths || 0),
        startDate: allocation?.depreciationStartDate || allocation?.created_at,
        rangeStart,
        rangeEnd,
        method: allocation?.depreciationMethod,
        residualValue: allocation?.residualValue,
    })
}

export const getStatementAllocationDepreciationRow = (allocation: any, rangeStart: any, rangeEnd: any) => {
    const totalAmount = Math.abs(Number(allocation?.amount || 0))
    const months = Number(allocation?.depreciationMonths || 0)
    const startDate = allocation?.depreciationStartDate || allocation?.created_at
    const method = normalizeDepreciationMethod(allocation?.depreciationMethod)
    const residualValue = Number(allocation?.residualValue || 0)
    const plan = getDepreciationPlan({
        amount: totalAmount,
        months,
        startDate,
        method,
        residualValue,
    })
    const amount = Number(plan.reduce((sum, item) => {
        const monthDate = dayjs(item.date)
        const periodStart = dayjs(rangeStart).startOf("month")
        const periodEnd = dayjs(rangeEnd).endOf("month")
        if ((monthDate.isAfter(periodStart) || monthDate.isSame(periodStart, "month"))
            && (monthDate.isBefore(periodEnd) || monthDate.isSame(periodEnd, "month"))) {
            return sum + Number(item.amount || 0)
        }
        return sum
    }, 0).toFixed(2))
    if (!amount) return null

    const alreadyDepreciated = Number(plan
        .filter((entry) => dayjs(entry.date).isBefore(dayjs().startOf("month")) || dayjs(entry.date).isSame(dayjs().startOf("month"), "month"))
        .reduce((sum, entry) => sum + Number(entry.amount || 0), 0)
        .toFixed(2))
    const residualBookValue = Number(Math.max(0, totalAmount - alreadyDepreciated).toFixed(2))

    return {
        source: "statementallocation",
        sourceId: allocation?.id,
        mode: normalizeExpenseBookingMode(allocation?.bookingMode),
        label: allocation?.depreciationLabel || allocation?.description || "Direkte Abschreibung",
        group: allocation?.depreciationGroup || null,
        amount,
        months,
        startDate,
        method,
        residualValue,
        alreadyDepreciated,
        residualBookValue,
    }
}

export const getAssetDepreciationStatus = (asset: any, asOfDate?: any) => {
    const amount = Number(asset?.amount || asset?.amountNet || 0)
    const months = Number(asset?.depreciationMonths || 0)
    const startDate = asset?.depreciationStartDate || asset?.date || asset?.created_at
    const method = normalizeDepreciationMethod(asset?.depreciationMethod)
    const residualValue = Number(asset?.residualValue || 0)
    const plan = getDepreciationPlan({
        amount,
        months,
        startDate,
        method,
        residualValue,
    })
    const cutoff = dayjs(asOfDate || new Date()).endOf("month")
    const depreciated = Number(plan
        .filter((entry) => dayjs(entry.date).isBefore(cutoff) || dayjs(entry.date).isSame(cutoff, "month"))
        .reduce((sum, entry) => sum + Number(entry.amount || 0), 0)
        .toFixed(2))
    const remaining = Number(Math.max(0, amount - depreciated).toFixed(2))
    const depreciableBase = Number(Math.max(0, amount - residualValue).toFixed(2))

    return {
        plan,
        amount: Number(amount.toFixed(2)),
        depreciated,
        remaining,
        residualValue: Number(residualValue.toFixed(2)),
        depreciableBase,
        progressPercent: depreciableBase > 0 ? Math.min(100, Number(((depreciated / depreciableBase) * 100).toFixed(2))) : 0,
    }
}
