import BaseService from './BaseService.js';
import { Sequelize, Op } from 'sequelize';
import { sequelize } from '../utils/sequelize.js';
import FalukantPredefineFirstname from '../models/falukant/predefine/firstname.js';
import FalukantPredefineLastname from '../models/falukant/predefine/lastname.js';
import FalukantUser from '../models/falukant/data/user.js';
import FalukantCharacter from '../models/falukant/data/character.js';
import RegionData from '../models/falukant/data/region.js';
import RegionType from '../models/falukant/type/region.js';
import FalukantStock from '../models/falukant/data/stock.js';
import FalukantStockType from '../models/falukant/type/stock.js';
import TitleOfNobility from '../models/falukant/type/title_of_nobility.js';
import Branch from '../models/falukant/data/branch.js';
import BranchType from '../models/falukant/type/branch.js';
import Production from '../models/falukant/data/production.js';
import ProductType from '../models/falukant/type/product.js';
import Knowledge from '../models/falukant/data/product_knowledge.js';
import Inventory from '../models/falukant/data/inventory.js';
import MoneyFlow from '../models/falukant/log/moneyflow.js';
import User from '../models/community/user.js';
import { notifyUser } from '../utils/socket.js';
import { differenceInDays } from 'date-fns';
import { updateFalukantUserMoney } from '../utils/sequelize.js';
import BuyableStock from '../models/falukant/data/buyable_stock.js';
import DirectorProposal from '../models/falukant/data/director_proposal.js';
import Director from '../models/falukant/data/director.js';
import DaySell from '../models/falukant/log/daysell.js';
import MarriageProposal from '../models/falukant/data/marriage_proposal.js';
import RelationshipType from '../models/falukant/type/relationship.js';
import Relationship from '../models/falukant/data/relationship.js';
import PromotionalGift from '../models/falukant/type/promotional_gift.js';
import PromotionalGiftCharacterTrait from '../models/falukant/predefine/promotional_gift_character_trait.js';
import PromotionalGiftMood from '../models/falukant/predefine/promotional_gift_mood.js';
import PromotionalGiftLog from '../models/falukant/log/promotional_gift.js';
import CharacterTrait from '../models/falukant/type/character_trait.js';
import Mood from '../models/falukant/type/mood.js';
import UserHouse from '../models/falukant/data/user_house.js';
import HouseType from '../models/falukant/type/house.js';
import BuyableHouse from '../models/falukant/data/buyable_house.js';
import PartyType from '../models/falukant/type/party.js';
import MusicType from '../models/falukant/type/music.js';
import BanquetteType from '../models/falukant/type/banquette.js';
import Party from '../models/falukant/data/party.js';
import ChildRelation from '../models/falukant/data/child_relation.js';
import Learning from '../models/falukant/data/learning.js';
import LearnRecipient from '../models/falukant/type/learn_recipient.js';
import Credit from '../models/falukant/data/credit.js';
import TitleRequirement from '../models/falukant/type/title_requirement.js';
import HealthActivity from '../models/falukant/log/health_activity.js';
import Election from '../models/falukant/data/election.js';
import PoliticalOfficeType from '../models/falukant/type/political_office_type.js';
import Candidate from '../models/falukant/data/candidate.js';
import Vote from '../models/falukant/data/vote.js';
import PoliticalOfficePrerequisite from '../models/falukant/predefine/political_office_prerequisite.js';
import PoliticalOfficeHistory from '../models/falukant/log/political_office_history.js';
import UndergroundType from '../models/falukant/type/underground.js';
import Notification from '../models/falukant/log/notification.js';
import PoliticalOffice from '../models/falukant/data/political_office.js';
import Underground from '../models/falukant/data/underground.js';

function calcAge(birthdate) {
    const b = new Date(birthdate); b.setHours(0, 0);
    const now = new Date(); now.setHours(0, 0);
    return differenceInDays(now, b);
}

async function getFalukantUserOrFail(hashedId) {
    const user = await FalukantUser.findOne({
        include: [{ model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } }]
    });
    if (!user) throw new Error('User not found');
    return user;
}

async function getBranchOrFail(userId, branchId) {
    const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: userId } });
    if (!branch) throw new Error('Branch not found');
    return branch;
}

function calcSellPrice(product, knowledgeFactor = 0) {
    const max = product.sellCost;
    const min = max * 0.6;
    return min + (max - min) * (knowledgeFactor / 100);
}

function calculateMarriageCost(titleOfNobility, age) {
    const minTitle = 1;
    const adjustedTitle = titleOfNobility - minTitle + 1;
    const baseCost = 500;
    return baseCost * Math.pow(adjustedTitle, 1.3) - (age - 12) * 20;
}

class PreconditionError extends Error {
    constructor(label) {
        super(label);
        this.name = 'PreconditionError';
        this.status = 412;
    }
}

class FalukantService extends BaseService {
    static KNOWLEDGE_MAX = 99;
    static COST_CONFIG = {
        one: { min: 50, max: 5000 },
        all: { min: 400, max: 40000 }
    };
    static HEALTH_ACTIVITIES = [
        { tr: "barber", method: "healthBarber", cost: 10 },
        { tr: "doctor", method: "healthDoctor", cost: 50 },
        { tr: "witch", method: "healthWitch", cost: 500 },
        { tr: "pill", method: "healthPill", cost: 5000 },
        { tr: "drunkOfLife", method: "healthDruckOfLife", cost: 5000000 }
    ];

    static RECURSIVE_REGION_SEARCH = `
        WITH RECURSIVE ancestors AS (
          SELECT
            r.id,
            r.parent_id
          FROM falukant_data.region r
          join falukant_data."character" c 
            on c.region_id = r.id
          WHERE c.user_id = :user_id
          UNION ALL
          SELECT
            r.id,
            r.parent_id
          FROM falukant_data.region r
          JOIN ancestors a ON r.id = a.parent_id
        )
        SELECT id
        FROM ancestors;
    `;

    async getFalukantUserByHashedId(hashedId) {
        console.log('🔍 getFalukantUserByHashedId called with hashedId:', hashedId);
        const user = await FalukantUser.findOne({
            include: [
                { model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } },
                {
                    model: FalukantCharacter,
                    as: 'character',
                    include: [
                        { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
                        { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
                        { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr', 'id'] },
                        { model: CharacterTrait, as: 'traits', attributes: ['id', 'tr'] }
                    ],
                    attributes: ['id', 'birthdate', 'gender', 'moodId', 'health']
                },
                {
                    model: UserHouse,
                    as: 'userHouse',
                    attributes: ['roofCondition', 'wallCondition', 'floorCondition', 'windowCondition'],
                    include: [
                        {
                            model: HouseType,
                            as: 'houseType',
                            attributes: ['labelTr', 'position']
                        }
                    ]
                },
            ]
        });
        console.log('🔍 getFalukantUserByHashedId result:', user ? 'User found' : 'User not found');
        if (!user) throw new Error('User not found');
        return user;
    }

    async getUser(hashedUserId) {
        const u = await FalukantUser.findOne({
            include: [
                { model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId: hashedUserId } },
                {
                    model: FalukantCharacter,
                    as: 'character',
                    include: [
                        { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
                        { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
                        { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }
                    ],
                    attributes: ['birthdate', 'gender']
                },
                {
                    model: RegionData,
                    as: 'mainBranchRegion',
                    include: [{ model: RegionType, as: 'regionType' }],
                    attributes: ['name']
                },
                {
                    model: Branch,
                    as: 'branches',
                    include: [
                        { model: BranchType, as: 'branchType', attributes: ['labelTr'] },
                        {
                            model: RegionData,
                            as: 'region',
                            include: [{ model: RegionType, as: 'regionType' }],
                            attributes: ['name']
                        }
                    ]
                },
                {
                    model: UserHouse,
                    as: 'userHouse',
                    include: [
                        {
                            model: HouseType,
                            as: 'houseType',
                            'attributes': ['labelTr', 'position']
                        },
                    ],
                    attributes: ['roofCondition'],
                },
            ],
            attributes: ['money', 'creditAmount', 'todayCreditTaken',]
        });
        if (!u) throw new Error('User not found');
        if (u.character?.birthdate) u.character.setDataValue('age', calcAge(u.character.birthdate));
        return u;
    }

    async randomFirstName(gender) {
        const names = await FalukantPredefineFirstname.findAll({ where: { gender } });
        return names[Math.floor(Math.random() * names.length)].name;
    }

    async randomLastName() {
        const names = await FalukantPredefineLastname.findAll();
        return names[Math.floor(Math.random() * names.length)].name;
    }

    async createUser(hashedUserId, gender, firstName, lastName) {
        const user = await this.getUserByHashedId(hashedUserId);
        if (await FalukantUser.findOne({ where: { userId: user.id } })) throw new Error('User already exists in Falukant.');
        let fnObj = await FalukantPredefineFirstname.findOne({ where: { name: firstName } });
        let lnObj = await FalukantPredefineLastname.findOne({ where: { name: lastName } });
        if (!fnObj) fnObj = await FalukantPredefineFirstname.create({ name: firstName, gender });
        if (!lnObj) lnObj = await FalukantPredefineLastname.create({ name: lastName });
        const region = await RegionData.findOne({
            order: Sequelize.fn('RANDOM'),
            limit: 1,
            include: [{ model: RegionType, as: 'regionType', where: { labelTr: 'city' } }]
        });
        if (!region) throw new Error('No region found with the label "city".');
        const nobility = await TitleOfNobility.findOne({ where: { labelTr: 'noncivil' } });
        if (!nobility) throw new Error('No title of nobility found with the label "noncivil".');
        const falukantUser = await FalukantUser.create({
            userId: user.id, money: 50, creditAmount: 0, todayCreditTaken: 0, creditInterestRate: 0, mainBranchRegionId: region.id
        });
        const date = new Date(); date.setDate(date.getDate() - 14);
        const ch = await FalukantCharacter.create({
            userId: falukantUser.id, regionId: region.id, firstName: fnObj.id, lastName: lnObj.id, gender, birthdate: date, titleOfNobility: nobility.id
        });
        falukantUser.character = ch;
        const bType = await BranchType.findOne({ where: { labelTr: 'fullstack' } });
        const branch = await Branch.create({ falukantUserId: falukantUser.id, regionId: region.id, branchTypeId: bType.id });
        const stType = await FalukantStockType.findOne({ where: [{ label_tr: 'wood' }] });
        await FalukantStock.create({ branchId: branch.id, stockTypeId: stType.id, quantity: 20 });
        notifyUser(user.hashedId, 'reloadmenu', {});
        return falukantUser;
    }

    async getInfo(hashedUserId) {
        const user = await User.findOne({ where: { hashedId: hashedUserId } });
        if (!user) throw new Error('User not found');
        const falukantUser = await FalukantUser.findOne({
            include: [
                { model: User, as: 'user', attributes: ['hashedId'], where: { hashedId: hashedUserId } },
                {
                    model: FalukantCharacter,
                    as: 'character',
                    attributes: ['birthdate', 'health'],
                    include: [
                        {
                            model: Relationship,
                            as: 'relationshipsAsCharacter1',
                            required: false,
                            attributes: ['id', 'character2Id', 'relationshipTypeId'],
                            include: [{
                                model: RelationshipType,
                                as: 'relationshipType',
                                attributes: ['tr'],
                                where: { tr: { [Op.not]: 'lover' } }
                            }]
                        },
                        {
                            model: Relationship,
                            as: 'relationshipsAsCharacter2',
                            required: false,
                            attributes: ['id', 'character1Id', 'relationshipTypeId'],
                            include: [{
                                model: RelationshipType,
                                as: 'relationshipType',
                                attributes: ['tr'],
                                where: { tr: { [Op.not]: 'lover' } }
                            }]
                        }
                    ]
                },
            ],
            attributes: ['id', 'money']
        });
        if (!falukantUser) throw new Error('User not found');
        if (falukantUser.character?.birthdate) falukantUser.character.setDataValue('age', calcAge(falukantUser.character.birthdate));

        // Aggregate status additions: children counts and unread notifications
        try {
            const bm = (step, payload = {}) => {
                try { console.log(`[BLOCKMARKER][falukant.getInfo] ${step}`, payload); } catch (_) { /* ignore */ }
            };
            bm('aggregate.start', { userId: user.id, falukantUserId: falukantUser.id });
            // Determine all character IDs belonging to the user
            if (!falukantUser.id) {
                bm('aggregate.noFalukantUserId');
                throw new Error('Missing falukantUser.id in getInfo aggregation');
            }
            const userCharacterIdsRows = await FalukantCharacter.findAll({
                attributes: ['id'],
                where: { userId: falukantUser.id },
                raw: true
            });
            const userCharacterIds = userCharacterIdsRows.map(r => r.id);
            bm('aggregate.userCharacters', { count: userCharacterIds.length, ids: userCharacterIds.slice(0, 5) });

            // Count distinct children for any of the user's characters (as father or mother)
            let childrenCount = 0;
            let unbaptisedChildrenCount = 0;
            if (userCharacterIds.length > 0) {
                const childRels = await ChildRelation.findAll({
                    attributes: ['childCharacterId'],
                    where: {
                        [Op.or]: [
                            { fatherCharacterId: { [Op.in]: userCharacterIds } },
                            { motherCharacterId: { [Op.in]: userCharacterIds } },
                        ]
                    },
                    raw: true
                });
                const distinctChildIds = new Set(childRels.map(r => r.childCharacterId));
                childrenCount = distinctChildIds.size;
                bm('aggregate.children', { relations: childRels.length, distinct: childrenCount, sample: Array.from(distinctChildIds).slice(0, 5) });

                const unbaptised = await ChildRelation.findAll({
                    attributes: ['childCharacterId'],
                    where: {
                        nameSet: false,
                        [Op.or]: [
                            { fatherCharacterId: { [Op.in]: userCharacterIds } },
                            { motherCharacterId: { [Op.in]: userCharacterIds } },
                        ]
                    },
                    raw: true
                });
                const distinctUnbaptisedIds = new Set(unbaptised.map(r => r.childCharacterId));
                unbaptisedChildrenCount = distinctUnbaptisedIds.size;
                bm('aggregate.unbaptised', { relations: unbaptised.length, distinct: unbaptisedChildrenCount, sample: Array.from(distinctUnbaptisedIds).slice(0, 5) });
            }

            // Unread notifications count
            const unreadNotifications = await Notification.count({ where: { userId: falukantUser.id, shown: false } });
            bm('aggregate.unread', { unreadNotifications });

            falukantUser.setDataValue('childrenCount', childrenCount);
            falukantUser.setDataValue('unbaptisedChildrenCount', unbaptisedChildrenCount);
            falukantUser.setDataValue('unreadNotifications', unreadNotifications);
            bm('aggregate.done', { childrenCount, unbaptisedChildrenCount });
        } catch (e) {
            console.error('Error aggregating status info:', e);
            falukantUser.setDataValue('childrenCount', 0);
            falukantUser.setDataValue('unbaptisedChildrenCount', 0);
            falukantUser.setDataValue('unreadNotifications', 0);
        }

        return falukantUser;
    }

    async getBranches(hashedUserId) {
        const u = await getFalukantUserOrFail(hashedUserId);
        const bs = await Branch.findAll({
            where: { falukantUserId: u.id },
            include: [
                { model: BranchType, as: 'branchType', attributes: ['labelTr'] },
                { model: RegionData, as: 'region', attributes: ['name'] }
            ],
            attributes: ['id', 'regionId'],
            order: [['branchTypeId', 'ASC']]
        });
        return bs.map(b => ({ ...b.toJSON(), isMainBranch: u.mainBranchRegionId === b.regionId }));
    }

    async createBranch(hashedUserId, cityId, branchTypeId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const branchType = await BranchType.findByPk(branchTypeId);
        if (!branchType) {
            throw new Error(`Unknown branchTypeId ${branchTypeId}`);
        }
        const existingCount = await Branch.count({
            where: { falukantUserId: user.id }
        });
        const exponentBase = Math.max(existingCount, 1);
        const rawCost = branchType.baseCost * Math.pow(exponentBase, 1.2);
        const cost = Math.round(rawCost * 100) / 100;
        await updateFalukantUserMoney(
            user.id,
            -cost,
            'create_branch'
        );
        const branch = await Branch.create({
            branchTypeId,
            regionId: cityId,
            falukantUserId: user.id
        });
        return branch;
    }

    async getBranchTypes(hashedUserId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const branchTypes = await BranchType.findAll();
        return branchTypes;

    }

    async getBranch(hashedUserId, branchId) {
        const u = await getFalukantUserOrFail(hashedUserId);
        const br = await Branch.findOne({
            where: { id: branchId, falukantUserId: u.id },
            include: [
                { model: BranchType, as: 'branchType', attributes: ['labelTr'] },
                { model: RegionData, as: 'region', attributes: ['name'] },
                {
                    model: Production,
                    as: 'productions',
                    attributes: ['quantity', 'startTimestamp'],
                    include: [{ model: ProductType, as: 'productType', attributes: ['id', 'category', 'labelTr', 'sellCost', 'productionTime'] }]
                }
            ],
            attributes: ['id', 'regionId']
        });
        if (!br) throw new Error('Branch not found');
        return br;
    }

    async getStock(hashedUserId, branchId) {
        const u = await getFalukantUserOrFail(hashedUserId);
        const b = await getBranchOrFail(u.id, branchId);
        return FalukantStock.findAll({ where: { regionId: b.regionId, userId: u.id } });
    }

    async createStock(hashedUserId, branchId, stockData) {
        const u = await getFalukantUserOrFail(hashedUserId);
        const b = await getBranchOrFail(u.id, branchId);
        return FalukantStock.create({
            userId: u.id, regionId: b.regionId, stockTypeId: stockData.stockTypeId, quantity: stockData.quantity
        });
    }

    async createProduction(hashedUserId, branchId, productId, quantity) {
        const u = await getFalukantUserOrFail(hashedUserId);
        const b = await getBranchOrFail(u.id, branchId);
        const p = await ProductType.findOne({ where: { id: productId } });
        const runningProductions = await Production.findAll({ where: { branchId: b.id } });
        if (runningProductions.length >= 2) {
            throw new Error('Too many productions');
            return; // wird später implementiert, wenn familie implementiert ist.

        }
        if (!p) throw new Error('Product not found');
        quantity = Math.min(100, quantity);
        const cost = quantity * p.category * 6;
        if (u.money < cost) throw new Error('notenoughmoney');
        const r = await updateFalukantUserMoney(u.id, -cost, 'Production cost', u.id);
        if (!r.success) throw new Error('Failed to update money');
        const d = await Production.create({ branchId: b.id, productId, quantity });
        notifyUser(u.user.hashedId, 'falukantUpdateStatus', {});
        notifyUser(u.user.hashedId, 'falukantBranchUpdate', { branchId: b.id });
        return d;
    }

    async getProduction(hashedUserId, branchId) {
        const u = await getFalukantUserOrFail(hashedUserId);
        const b = await getBranchOrFail(u.id, branchId);
        return Production.findOne({ where: { regionId: b.regionId } });
    }

    async getProducts(hashedUserId) {
        const u = await getFalukantUserOrFail(hashedUserId);
        const c = await FalukantCharacter.findOne({ where: { userId: u.id } });
        if (!c) {
            throw new Error(`No FalukantCharacter found for user with id ${u.id}`);
        }
        const ps = await ProductType.findAll({
            where: { category: { [Op.lte]: u.certificate } },
            include: [{ model: Knowledge, as: 'knowledges', attributes: ['knowledge'], where: { characterId: c.id } }],
            attributes: ['labelTr', 'id', 'sellCost', 'productionTime', 'category']
        });
        return ps;
    }

    async getInventory(hashedUserId, branchId) {
        const u = await getFalukantUserOrFail(hashedUserId);
        const f = branchId ? { id: branchId, falukantUserId: u.id } : { falukantUserId: u.id };
        const br = await Branch.findAll({
            where: f,
            include: [
                { model: FalukantStock, as: 'stocks', include: [{ model: FalukantStockType, as: 'stockType' }] },
                { model: RegionData, as: 'region', include: [{ model: RegionType, as: 'regionType' }] }
            ]
        });
        const stockIds = br.flatMap(b => b.stocks.map(s => s.id));
        const inv = await Inventory.findAll({
            where: { stockId: stockIds },
            include: [
                {
                    model: FalukantStock,
                    as: 'stock',
                    include: [
                        {
                            model: Branch,
                            as: 'branch',
                            include: [{ model: RegionData, as: 'region', include: [{ model: RegionType, as: 'regionType' }] }]
                        },
                        { model: FalukantStockType, as: 'stockType' }
                    ]
                },
                { model: ProductType, as: 'productType' }
            ]
        });
        const grouped = inv.reduce((acc, i) => {
            const r = i.stock.branch.region;
            const k = `${r.id}-${i.productType.id}-${i.quality}`;
            acc[k] = acc[k] || { region: r, product: i.productType, quality: i.quality, totalQuantity: 0 };
            acc[k].totalQuantity += i.quantity;
            return acc;
        }, {});
        return Object.values(grouped).sort((a, b) => {
            if (a.region.id !== b.region.id) return a.region.id - b.region.id;
            if (a.product.id !== b.product.id) return a.product.id - b.product.id;
            return a.quality - b.quality;
        });
    }

    async sellProduct(hashedUserId, branchId, productId, quality, quantity) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const branch = await getBranchOrFail(user.id, branchId);
        const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
        if (!character) throw new Error('No character found for user');
        const stock = await FalukantStock.findOne({ where: { branchId: branch.id } });
        if (!stock) throw new Error('Stock not found');
        const inventory = await Inventory.findAll({
            where: { quality },
            include: [
                {
                    model: ProductType,
                    as: 'productType',
                    required: true,
                    where: { id: productId },
                    include: [
                        {
                            model: Knowledge,
                            as: 'knowledges',
                            required: false,
                            where: { characterId: character.id }
                        }
                    ]
                }
            ]
        });
        if (!inventory.length) {
            throw new Error('No inventory found');
        }
        const available = inventory.reduce((sum, i) => sum + i.quantity, 0);
        if (available < quantity) throw new Error('Not enough inventory available');
        const item = inventory[0].productType;
        const knowledgeVal = item.knowledges?.[0]?.knowledge || 0;
        const revenue = quantity * calcSellPrice(item, knowledgeVal);
        const moneyResult = await updateFalukantUserMoney(user.id, revenue, 'Product sale', user.id);
        if (!moneyResult.success) throw new Error('Failed to update money');
        let remaining = quantity;
        for (const inv of inventory) {
            if (inv.quantity <= remaining) {
                remaining -= inv.quantity;
                await inv.destroy();
            } else {
                await inv.update({ quantity: inv.quantity - remaining });
                remaining = 0;
                break;
            }
        }
        await this.addSellItem(branchId, user.id, productId, quantity);
        console.log('[FalukantService.sellProduct] emitting events for user', user.user.hashedId, 'branch', branch?.id);
        notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
        notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id });
        return { success: true };
    }

    async sellAllProducts(hashedUserId, branchId) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);
        const branch = await Branch.findOne({
            where: { id: branchId, falukantUserId: falukantUser.id },
            include: [{ model: FalukantStock, as: 'stocks' }]
        });
        if (!branch) throw new Error('Branch not found');
        const stockIds = branch.stocks.map(s => s.id);
        const character = await FalukantCharacter.findOne({ where: { userId: falukantUser.id } });
        if (!character) throw new Error('No character for user');
        const inventory = await Inventory.findAll({
            where: { stockId: stockIds },
            include: [
                {
                    model: ProductType,
                    as: 'productType',
                    include: [
                        {
                            model: Knowledge,
                            as: 'knowledges',
                            required: false,
                            where: {
                                characterId: character.id
                            }
                        }
                    ]
                },
                {
                    model: FalukantStock,
                    as: 'stock',
                    include: [
                        {
                            model: Branch,
                            as: 'branch'
                        },
                        {
                            model: FalukantStockType,
                            as: 'stockType'
                        }
                    ]
                }
            ]
        });
        if (!inventory.length) return { success: true, revenue: 0 };
        let total = 0;
        for (const item of inventory) {
            const knowledgeVal = item.productType.knowledges[0]?.knowledge || 0;
            total += item.quantity * calcSellPrice(item.productType, knowledgeVal);
            await this.addSellItem(item.stock.branch.id, falukantUser.id, item.productType.id, item.quantity);
        }
        const moneyResult = await updateFalukantUserMoney(
            falukantUser.id,
            total,
            'Sell all products',
            falukantUser.id
        );
        if (!moneyResult.success) throw new Error('Failed to update money');
        for (const item of inventory) {
            await Inventory.destroy({ where: { id: item.id } });
        }
        console.log('[FalukantService.sellAllProducts] emitting events for user', falukantUser.user.hashedId, 'branch', branchId, 'revenue', total, 'items', inventory.length);
        notifyUser(falukantUser.user.hashedId, 'falukantUpdateStatus', {});
        notifyUser(falukantUser.user.hashedId, 'falukantBranchUpdate', { branchId });
        return { success: true, revenue: total };
    }

    async addSellItem(branchId, userId, productId, quantity) {
        const branch = await Branch.findOne({
            where: { id: branchId },
        })
            ;
        const daySell = await DaySell.findOne({
            where: {
                regionId: branch.regionId,
                productId: productId,
                sellerId: userId,
            }
        });
        if (daySell) {
            daySell.quantity += quantity;
            await daySell.save();
        } else {
            await DaySell.create({
                regionId: branch.regionId,
                productId: productId,
                sellerId: userId,
                quantity: quantity,
            });
        }
    }

    async moneyHistory(hashedUserId, page = 1, filter = '') {
        const u = await getFalukantUserOrFail(hashedUserId);
        const limit = 25, offset = (page - 1) * limit;
        const w = { falukantUserId: u.id };
        if (filter) w.activity = { [Op.iLike]: `%${filter}%` };
        const { rows, count } = await MoneyFlow.findAndCountAll({
            where: w, order: [['time', 'DESC']], limit, offset
        });
        return { data: rows, total: count, currentPage: page, totalPages: Math.ceil(count / limit) };
    }

    async getStorage(hashedUserId, branchId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const branch = await getBranchOrFail(user.id, branchId);
        const stocks = await FalukantStock.findAll({
            where: { branchId: branch.id },
            include: [{ model: FalukantStockType, as: 'stockType' }],
        });
        const stockIds = stocks.map(s => s.id);
        const inventoryItems = await Inventory.findAll({
            where: { stockId: stockIds },
            include: [
                {
                    model: FalukantStock,
                    as: 'stock',
                    include: [{ model: FalukantStockType, as: 'stockType' }],
                },
            ],
        });
        let totalUsedCapacity = 0;
        const usageByType = {};
        for (const s of stocks) {
            const stId = s.stockTypeId;
            if (!usageByType[stId]) {
                usageByType[stId] = {
                    stockTypeId: stId,
                    stockTypeLabelTr: s.stockType?.labelTr || `stockType:${stId}`,
                    totalCapacity: 0,
                    used: 0
                };
            }
            usageByType[stId].totalCapacity += s.quantity;
        }
        for (const item of inventoryItems) {
            totalUsedCapacity += item.quantity;
            const stId = item.stock.stockTypeId;
            if (!usageByType[stId]) {
                usageByType[stId] = {
                    stockTypeId: stId,
                    stockTypeLabelTr: item.stock.stockType?.labelTr || `stockType:${stId}`,
                    totalCapacity: 0,
                    used: 0
                };
            }
            usageByType[stId].used += item.quantity;
        }
        const buyableStocks = await BuyableStock.findAll({
            where: { regionId: branch.regionId },
            include: [{ model: FalukantStockType, as: 'stockType' }],
        });
        const buyableByType = {};
        for (const b of buyableStocks) {
            const stId = b.stockTypeId;
            if (!buyableByType[stId]) {
                buyableByType[stId] = {
                    stockTypeId: stId,
                    stockTypeLabelTr: b.stockType?.labelTr || `stockType:${stId}`,
                    quantity: 0
                };
            }
            buyableByType[stId].quantity += b.quantity;
        }
        let maxCapacity = stocks.reduce((sum, s) => sum + s.quantity, 0);
        return {
            branchId,
            totalUsedCapacity,
            maxCapacity,
            usageByType: Object.values(usageByType),
            buyableByType: Object.values(buyableByType)
        };
    }

    async buyStorage(hashedUserId, branchId, amount, stockTypeId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const branch = await getBranchOrFail(user.id, branchId);
        const buyableStocks = await BuyableStock.findAll({
            where: { regionId: branch.regionId, stockTypeId },
            include: [{ model: FalukantStockType, as: 'stockType' }]
        });
        if (!buyableStocks || buyableStocks.length === 0) {
            throw new Error('Not enough buyable stock');
        }
        const totalAvailable = buyableStocks.reduce((sum, entry) => sum + entry.quantity, 0);
        if (totalAvailable < amount) {
            throw new Error('Not enough buyable stock');
        }
        const costPerUnit = buyableStocks[0].stockType.cost;
        const totalCost = costPerUnit * amount;
        if (user.money < totalCost) {
            throw new Error('notenoughmoney');
        }
        const moneyResult = await updateFalukantUserMoney(
            user.id,
            -totalCost,
            `Buy storage (type: ${buyableStocks[0].stockType.labelTr})`,
            user.id
        );
        if (!moneyResult.success) {
            throw new Error('Failed to update money');
        }
        let remainingToDeduct = amount;
        for (const entry of buyableStocks) {
            if (entry.quantity > remainingToDeduct) {
                entry.quantity -= remainingToDeduct;
                await entry.save();
                remainingToDeduct = 0;
                break;
            } else if (entry.quantity === remainingToDeduct) {
                await entry.destroy();
                remainingToDeduct = 0;
                break;
            } else {
                remainingToDeduct -= entry.quantity;
                await entry.destroy();
            }
        }
        let stock = await FalukantStock.findOne({
            where: { branchId: branch.id, stockTypeId },
            include: [{ model: FalukantStockType, as: 'stockType' }]
        });
        if (!stock) {
            stock = await FalukantStock.create({
                branchId: branch.id,
                stockTypeId,
                quantity: amount,
            });
        } else {
            stock.quantity += amount;
            await stock.save();
        }
        notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
        notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId });

        return {
            success: true,
            bought: amount,
            totalCost,
            stockType: buyableStocks[0].stockType.labelTr
        };
    }

    async sellStorage(hashedUserId, branchId, amount, stockTypeId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const branch = await getBranchOrFail(user.id, branchId);
        const stock = await FalukantStock.findOne({
            where: { branchId: branch.id, stockTypeId },
            include: [{ model: FalukantStockType, as: 'stockType' }]
        });
        if (!stock || stock.quantity < amount) throw new Error('Not enough stock to sell');
        const costPerUnit = stock.stockType.cost;
        const totalRevenue = costPerUnit * amount;
        const moneyResult = await updateFalukantUserMoney(
            user.id,
            totalRevenue,
            `Sell storage (type: ${stock.stockType.labelTr})`,
            user.id
        );
        if (!moneyResult.success) throw new Error('Failed to update money');
        stock.quantity -= amount;
        await stock.save();
        const buyable = await BuyableStock.findOne({
            where: { regionId: branch.regionId, stockTypeId },
            include: [{ model: FalukantStockType, as: 'stockType' }]
        });
        if (!buyable) throw new Error('No buyable record found for this region and stockType');
        buyable.quantity += amount;
        await buyable.save();
        return { success: true, sold: amount, totalRevenue, stockType: stock.stockType.labelTr };
    }

    async notifyRegionUsersAboutStockChange(regionId) {
        const users = await FalukantCharacter.findAll({
            where: {
                regionId: regionId
            },
            include: [
                {
                    model: FalukantUser,
                    as: 'user',
                    include: [
                        {
                            model: User,
                            as: 'user'
                        },
                        {
                            model: Branch,
                            as: 'branch',
                            where: {
                                regionId: regionId
                            }
                        }
                    ]
                }
            ]
        });
        for (const user of users) {
            notifyUser(user.user[0].user[0].hashedId, 'stock_change', { branchId: user.user[0].branch[0].id });
        }
    }

    async getStockTypes() {
        return FalukantStockType.findAll();
    }

    async getStockOverview() {
        const items = await Inventory.findAll({
            include: [
                {
                    model: FalukantStock,
                    as: 'stock',
                    include: [
                        {
                            model: Branch,
                            as: 'branch',
                            include: [{ model: RegionData, as: 'region' }]
                        }
                    ]
                },
                {
                    model: ProductType,
                    as: 'productType'
                }
            ]
        });
        const result = items.map(inv => ({
            regionName: inv.stock?.branch?.region?.name || '???',
            productLabelTr: inv.productType?.labelTr || '???',
            quantity: inv.quantity
        }));
        return result;
    }

    async getAllProductions(hashedUserId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const productions = await Production.findAll({
            include: [
                {
                    model: Branch,
                    as: 'branch',
                    include: [
                        { model: RegionData, as: 'region', attributes: ['name'] }
                    ],
                    where: { falukantUserId: user.id }
                },
                { model: ProductType, as: 'productType', attributes: ['labelTr', 'productionTime'] }
            ],
            attributes: ['startTimestamp', 'quantity'],
        });
        const formattedProductions = productions.map((production) => {
            const startTimestamp = new Date(production.startTimestamp).getTime();
            const endTimestamp = startTimestamp + production.productType.productionTime * 60 * 1000;
            return {
                cityName: production.branch.region.name,
                productName: production.productType.labelTr,
                quantity: production.quantity,
                endTimestamp: new Date(endTimestamp).toISOString(),
            };
        });
        formattedProductions.sort((a, b) => new Date(a.endTimestamp) - new Date(b.endTimestamp));
        return formattedProductions;
    }

    async getDirectorProposals(hashedUserId, branchId) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        if (!user) {
            throw new Error('User not found');
        }
        const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: user.id } });
        if (!branch) {
            throw new Error('Branch not found or does not belong to the user');
        }
        const { falukantUserId, regionId } = branch;
        await this.deleteExpiredProposals();
        const existingProposals = await this.fetchProposals(falukantUserId, regionId);
        if (existingProposals.length > 0) {
            return this.formatProposals(existingProposals);
        }
        await this.generateProposals(falukantUserId, regionId);
        const newProposals = await this.fetchProposals(falukantUserId, regionId);
        return this.formatProposals(newProposals);
    }

    async deleteExpiredProposals() {
        const expirationTime = new Date(Date.now() - 24 * 60 * 60 * 1000);
        await DirectorProposal.destroy({
            where: {
                createdAt: {
                    [Op.lt]: expirationTime,
                },
            },
        });
    }

    async fetchProposals(falukantUserId, regionId) {
        return DirectorProposal.findAll({
            where: { employerUserId: falukantUserId },
            include: [
                {
                    model: FalukantCharacter,
                    as: 'character',
                    attributes: ['firstName', 'lastName', 'birthdate', 'titleOfNobility', 'gender'],
                    where: { regionId },
                    include: [
                        { model: FalukantPredefineFirstname, as: 'definedFirstName' },
                        { model: FalukantPredefineLastname, as: 'definedLastName' },
                        { model: TitleOfNobility, as: 'nobleTitle' },
                        {
                            model: Knowledge,
                            as: 'knowledges',
                            include: [
                                { model: ProductType, as: 'productType' },
                            ]
                        },
                    ],
                },
            ],
        });
    }

    async generateProposals(falukantUserId, regionId) {
        try {
            const threeWeeksAgo = new Date(Date.now() - 21 * 24 * 60 * 60 * 1000);
            const proposalCount = Math.floor(Math.random() * 3) + 3;
            for (let i = 0; i < proposalCount; i++) {
                const directorCharacter = await FalukantCharacter.findOne({
                    where: {
                        regionId,
                        createdAt: { [Op.lt]: threeWeeksAgo },
                    },
                    include: [
                        {
                            model: TitleOfNobility,
                            as: 'nobleTitle',
                            attributes: ['level'],
                        },
                    ],
                    order: sequelize.literal('RANDOM()'),
                });
                if (!directorCharacter) {
                    throw new Error('No directors available for the region');
                }
                const avgKnowledge = await this.calculateAverageKnowledge(directorCharacter.id);
                const proposedIncome = Math.round(
                    directorCharacter.nobleTitle.level * Math.pow(1.231, avgKnowledge / 1.5)
                );
                await DirectorProposal.create({
                    directorCharacterId: directorCharacter.id,
                    employerUserId: falukantUserId,
                    proposedIncome,
                });
            }
        } catch (error) {
            console.log(error.message, error.stack);
            throw new Error(error.message);
        }
    }

    async calculateAverageKnowledge(characterId) {
        const averageKnowledge = await Knowledge.findAll({
            where: { characterId },
            attributes: [[Sequelize.fn('AVG', Sequelize.col('knowledge')), 'avgKnowledge']],
            raw: true,
        });
        return parseFloat(averageKnowledge[0]?.avgKnowledge || 0);
    }

    formatProposals(proposals) {
        return proposals.map((proposal) => {
            const age = Math.floor((Date.now() - new Date(proposal.character.birthdate)) / (24 * 60 * 60 * 1000));
            const knowledge = proposal.character.knowledges?.map(k => ({
                productId: k.productId,
                value: k.knowledge,
                labelTr: k.productType.labelTr,
            })) || [];
            return {
                id: proposal.id,
                proposedIncome: proposal.proposedIncome,
                character: {
                    name: `${proposal.character.definedFirstName.name} ${proposal.character.definedLastName.name}`,
                    title: proposal.character.nobleTitle.labelTr,
                    age,
                    knowledge,
                    gender: proposal.character.gender,
                },
            };
        });
    }

    async convertProposalToDirector(hashedUserId, proposalId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        if (!user) {
            throw new Error('User not found');
        }
        const proposal = await DirectorProposal.findOne(
            {
                where: { id: proposalId },
                include: [
                    { model: FalukantCharacter, as: 'character' },
                ]
            }
        );
        if (!proposal || proposal.employerUserId !== user.id) {
            throw new Error('Proposal does not belong to the user');
        }
        const existingDirector = await Director.findOne({
            where: {
                employerUserId: user.id
            },
            include: [
                {
                    model: FalukantCharacter,
                    as: 'character',
                    where: {
                        regionId: proposal.character.regionId,
                    }
                },
            ]
        });
        if (existingDirector) {
            throw new Error('A director already exists for this region');
        }
        const newDirector = await Director.create({
            directorCharacterId: proposal.directorCharacterId,
            employerUserId: proposal.employerUserId,
            income: proposal.proposedIncome,
        });
        const regionUserDirectorProposals = await DirectorProposal.findAll({
            where: {
                employerUserId: proposal.employerUserId,
            },
            include: [
                {
                    model: FalukantCharacter,
                    as: 'character',
                    where: {
                        regionId: proposal.character.regionId,
                    }
                },
            ]
        });
        if (regionUserDirectorProposals.length > 0) {
            for (const p of regionUserDirectorProposals) {
                await p.destroy();
            }
        }
        notifyUser(hashedUserId, 'directorchanged');
        return newDirector;
    }

    async getDirectorForBranch(hashedUserId, branchId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        if (!user) {
            throw new Error('User not found');
        }
        const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: user.id } });
        if (!branch) {
            throw new Error('Branch not found or does not belong to the user');
        }
        const director = await Director.findOne({
            where: { employerUserId: user.id },
            include: [
                {
                    model: FalukantCharacter,
                    as: 'character',
                    attributes: ['firstName', 'lastName', 'birthdate', 'titleOfNobility', 'gender'],
                    where: {
                        regionId: branch.regionId,
                    },
                    include: [
                        {
                            model: TitleOfNobility,
                            as: 'nobleTitle'
                        },
                        {
                            model: FalukantPredefineFirstname,
                            as: 'definedFirstName'
                        },
                        {
                            model: FalukantPredefineLastname,
                            as: 'definedLastName'
                        },
                    ]
                },
            ],
        });
        if (!director) {
            return null;
        }
        const age = Math.floor((Date.now() - new Date(director.character.birthdate)) / (24 * 60 * 60 * 1000));
        return {
            director: {
                id: director.id,
                character: {
                    name: `${director.character.definedFirstName.name} ${director.character.definedLastName.name}`,
                    title: director.character.nobleTitle.labelTr,
                    age,
                    gender: director.character.gender,
                },
                income: director.income,
                satisfaction: director.satisfaction,
                mayProduce: director.mayProduce,
                maySell: director.maySell,
                mayStartTransport: director.mayStartTransport,
            },
        };
    }

    async getAllDirectors(hashedUserId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        if (!user) {
            throw new Error('User not found');
        }
        const directors = await Director.findAll({
            where: { employerUserId: user.id },
            include: [
                {
                    model: FalukantCharacter,
                    as: 'character',
                    attributes: ['id', 'birthdate', 'gender'],
                    include: [
                        {
                            model: TitleOfNobility,
                            as: 'nobleTitle',
                            attributes: ['labelTr', 'level'],
                        },
                        {
                            model: FalukantPredefineFirstname,
                            as: 'definedFirstName',
                            attributes: ['name'],
                        },
                        {
                            model: FalukantPredefineLastname,
                            as: 'definedLastName',
                            attributes: ['name'],
                        },
                        {
                            model: Knowledge,
                            as: 'knowledges',
                            attributes: ['productId', 'knowledge'],
                            include: [
                                {
                                    model: ProductType,
                                    as: 'productType',
                                    attributes: ['labelTr'],
                                },
                            ],
                        },
                        {
                            model: RegionData,
                            as: 'region',
                            attributes: ['name']
                        }
                    ]
                },
            ],
            attributes: ['id', 'satisfaction', 'income'],
        });
        return directors.map(director => {
            // 1) avgKnowledge berechnen
            const knowledges = director.character.knowledges || [];
            const avgKnowledge = knowledges.length
                ? knowledges.reduce((sum, k) => sum + k.knowledge, 0) / knowledges.length
                : 0;

            // 2) wishedIncome anhand der JS-Formel
            const wishedIncome = Math.round(
                director.character.nobleTitle.level * Math.pow(1.231, avgKnowledge / 1.5)
            );

            return {
                id: director.id,
                satisfaction: director.satisfaction,
                character: director.character,
                age: calcAge(director.character.birthdate),
                income: director.income,
                region: director.character.region.name,
                wishedIncome,
            };
        });
    }

    async updateDirector(hashedUserId, directorId, income) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        const director = await Director.findOne({
            where: {
                id: directorId,
                employerUserId: user.id
            }
        });
        if (!director) {
            throw new Error('Director not found');
        }
        director.update({ income: income });
        return { success: true };
    }

    async setSetting(hashedUserId, branchId, directorId, settingKey, value) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        const branch = await Branch.findOne({
            where: {
                id: branchId,
                falukantUserId: user.id,
            },
        });
        if (!branch) {
            return null;
        }
        const director = await Director.findOne({
            where: {
                id: directorId,
                employerUserId: user.id,
            },
            include: [{
                model: FalukantCharacter,
                as: 'character',
                where: {
                    regionId: branch.regionId,
                }
            }]
        });
        if (!director) {
            return null;
        }
        const updateData = {};
        updateData[settingKey] = value || false;

        await Director.update(updateData, {
            where: {
                id: director.id,
            },
        });
        return { result: 'ok' };
    }

    async getFamily(hashedUserId) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        if (!user) throw new Error('User not found');
        const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
        if (!character) throw new Error('Character not found for this user');
        let relationships = await Relationship.findAll({
            where: { character1Id: character.id },
            attributes: ['createdAt', 'widowFirstName2', 'nextStepProgress'],
            include: [
                {
                    model: FalukantCharacter, as: 'character2',
                    attributes: ['id', 'birthdate', 'gender', 'moodId'],
                    include: [
                        { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
                        { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] },
                        { model: CharacterTrait, as: 'traits' },
                        { model: Mood, as: 'mood' },
                    ]
                },
                { model: RelationshipType, as: 'relationshipType', attributes: ['tr'] }
            ]
        });
        relationships = relationships.map(r => ({
            createdAt: r.createdAt,
            widowFirstName2: r.widowFirstName2,
            progress: r.nextStepProgress,
            character2: {
                id: r.character2.id,
                age: calcAge(r.character2.birthdate),
                gender: r.character2.gender,
                firstName: r.character2.definedFirstName?.name || 'Unknown',
                nobleTitle: r.character2.nobleTitle?.labelTr || '',
                mood: r.character2.mood,
                traits: r.character2.traits
            },
            relationshipType: r.relationshipType.tr
        }));
        const charsWithChildren = await FalukantCharacter.findAll({
            where: { userId: user.id },
            include: [
                {
                    model: ChildRelation,
                    as: 'childrenFather',
                    include: [{
                        model: FalukantCharacter,
                        as: 'child',
                        include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
                    }]
                },
                {
                    model: ChildRelation,
                    as: 'childrenMother',
                    include: [{
                        model: FalukantCharacter,
                        as: 'child',
                        include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
                    }]
                }
            ]
        });
    const children = [];
        for (const parentChar of charsWithChildren) {
            const allRels = [
                ...(parentChar.childrenFather || []),
                ...(parentChar.childrenMother || [])
            ];
            for (const rel of allRels) {
                const kid = rel.child;
                children.push({
                    name: kid.definedFirstName?.name || 'Unknown',
                    gender: kid.gender,
                    age: calcAge(kid.birthdate),
            hasName: rel.nameSet,
            _createdAt: rel.createdAt,
                });
            }
        }
    // Sort children globally by relation createdAt ascending (older first)
    children.sort((a, b) => new Date(a._createdAt) - new Date(b._createdAt));
        const inProgress = ['wooing', 'engaged', 'married'];
        const family = {
            relationships: relationships.filter(r => inProgress.includes(r.relationshipType)),
            lovers: relationships.filter(r => r.relationshipType === 'lover'),
            deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
            children: children.map(({ _createdAt, ...rest }) => rest),
            possiblePartners: []
        };
        const ownAge = calcAge(character.birthdate);
        if (ownAge >= 12 && family.relationships.length === 0) {
            family.possiblePartners = await this.getPossiblePartners(character.id);
            if (family.possiblePartners.length === 0) {
                await this.createPossiblePartners(
                    character.id,
                    character.gender,
                    character.regionId,
                    character.titleOfNobility,
                    ownAge
                );
                family.possiblePartners = await this.getPossiblePartners(character.id);
            }
        }
        return family;
    }

    async getPossiblePartners(requestingCharacterId) {
        const proposals = await MarriageProposal.findAll({
            where: {
                requesterCharacterId: requestingCharacterId,
            },
            include: [
                {
                    model: FalukantCharacter,
                    as: 'proposedCharacter',
                    attributes: ['id', 'firstName', 'lastName', 'gender', 'regionId', 'birthdate'],
                    include: [
                        { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
                        { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
                        { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] },
                    ],
                },
            ],
        });
        return proposals.map(proposal => {
            const birthdate = new Date(proposal.proposedCharacter.birthdate);
            const age = calcAge(birthdate);
            return {
                id: proposal.id,
                requesterCharacterId: proposal.requesterCharacterId,
                proposedCharacterId: proposal.proposedCharacter.id,
                proposedCharacterName: `${proposal.proposedCharacter.definedFirstName?.name} ${proposal.proposedCharacter.definedLastName?.name}`,
                proposedCharacterGender: proposal.proposedCharacter.gender,
                proposedCharacterRegionId: proposal.proposedCharacter.regionId,
                proposedCharacterAge: age,
                proposedCharacterNobleTitle: proposal.proposedCharacter.nobleTitle.labelTr,
                cost: proposal.cost,
            };
        });
    }

    async createPossiblePartners(requestingCharacterId, requestingCharacterGender, requestingRegionId, requestingCharacterTitleOfNobility, ownAge) {
        try {
            const minTitleResult = await TitleOfNobility.findOne({
                order: [['id', 'ASC']],
                attributes: ['id'],
            });
            if (!minTitleResult) {
                throw new Error('No title of nobility found');
            }
            const minTitle = minTitleResult.id;

            const potentialPartners = await FalukantCharacter.findAll({
                where: {
                    id: { [Op.ne]: requestingCharacterId },
                    gender: { [Op.ne]: requestingCharacterGender },
                    regionId: requestingRegionId,
                    createdAt: { [Op.lt]: new Date(new Date() - 12 * 24 * 60 * 60 * 1000) },
                    titleOfNobility: { [Op.between]: [requestingCharacterTitleOfNobility - 1, requestingCharacterTitleOfNobility + 1] }
                },
                order: [
                    [Sequelize.literal(`ABS((EXTRACT(EPOCH FROM (NOW() - "birthdate")) / 86400) - ${ownAge})`), 'ASC']
                ],
                limit: 5,
            });

            const proposals = potentialPartners.map(partner => {
                const age = calcAge(partner.birthdate);
                return {
                    requesterCharacterId: requestingCharacterId,
                    proposedCharacterId: partner.id,
                    cost: calculateMarriageCost(partner.titleOfNobility, age, minTitle),
                };
            });
            await MarriageProposal.bulkCreate(proposals);
        } catch (error) {
            console.error('Error creating possible partners:', error);
            throw error;
        }
    }

    async acceptMarriageProposal(hashedUserId, proposedCharacterId) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
        if (!user) {
            throw new Error('User not found');
        }
        const proposal = await MarriageProposal.findOne({
            where: {
                requesterCharacterId: character.id,
                proposedCharacterId: proposedCharacterId,
            },
        });
        if (!proposal) {
            throw new Error('Proposal not found');
        }
        if (user.money < proposal.cost) {
            throw new Error('Not enough money to accept the proposal');
        }
        const moneyResult = await updateFalukantUserMoney(user.id, -proposal.cost, 'Marriage cost', user.id);
        if (!moneyResult.success) {
            throw new Error('Failed to update money');
        }
        const marriedType = await RelationshipType.findOne({
            where: { tr: 'wooing' },
        });
        if (!marriedType) {
            throw new Error('Relationship type "married" not found');
        }
        await Relationship.create({
            character1Id: proposal.requesterCharacterId,
            character2Id: proposal.proposedCharacterId,
            relationshipTypeId: marriedType.id,
        });
        await MarriageProposal.destroy({
            where: { requesterCharacterId: character.id },
        })
            ;
        return { success: true, message: 'Marriage proposal accepted' };
    }

    async getGifts(hashedUserId) {
        // 1) Mein User & Character
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        const myChar = await FalukantCharacter.findOne({ where: { userId: user.id } });
        if (!myChar) throw new Error('Character not found');

        // 2) Beziehung finden und „anderen“ Character bestimmen
        const rel = await Relationship.findOne({
            where: {
                [Op.or]: [
                    { character1Id: myChar.id },
                    { character2Id: myChar.id }
                ]
            },
            include: [
                { model: FalukantCharacter, as: 'character1', include: [{ model: CharacterTrait, as: 'traits' }] },
                { model: FalukantCharacter, as: 'character2', include: [{ model: CharacterTrait, as: 'traits' }] }
            ]
        });
        if (!rel) throw new Error('Beziehung nicht gefunden');

        const relatedChar = rel.character1.id === myChar.id ? rel.character2 : rel.character1;

        // 3) Trait-IDs und Mood des relatedChar
        const relatedTraitIds = relatedChar.traits.map(t => t.id);
        const relatedMoodId = relatedChar.moodId;

        // 4) Gifts laden – aber nur die passenden Moods und Traits als Unter-Arrays
        const gifts = await PromotionalGift.findAll({
            include: [
                {
                    model: PromotionalGiftMood,
                    as: 'promotionalgiftmoods',
                    attributes: ['mood_id', 'suitability'],
                    where: { mood_id: relatedMoodId },
                    required: false    // Gifts ohne Mood-Match bleiben erhalten, haben dann leeres Array
                },
                {
                    model: PromotionalGiftCharacterTrait,
                    as: 'characterTraits',
                    attributes: ['trait_id', 'suitability'],
                    where: { trait_id: relatedTraitIds },
                    required: false    // Gifts ohne Trait-Match bleiben erhalten
                }
            ]
        });

        // 5) Rest wie gehabt: Kosten berechnen und zurückgeben
        const lowestTitleOfNobility = await TitleOfNobility.findOne({ order: [['id', 'ASC']] });
        return Promise.all(gifts.map(async gift => ({
            id: gift.id,
            name: gift.name,
            cost: await this.getGiftCost(
                gift.value,
                myChar.titleOfNobility,
                lowestTitleOfNobility.id
            ),
            moodsAffects: gift.promotionalgiftmoods,   // nur Einträge mit relatedMoodId
            charactersAffects: gift.characterTraits   // nur Einträge mit relatedTraitIds
        })));
    }

    async getChildren(hashedUserId) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        console.log(user);
        const children = await ChildRelation.findAll({
            where: {
                [Op.or]: [
                    { fatherCharacterId: user.character.id },
                    { motherCharacterId: user.character.id }
                ]
            },
            include: [
                {
                    model: FalukantCharacter,
                    as: 'child',
                    attributes: ['id', 'birthdate'],
                    include: [
                        {
                            model: FalukantPredefineFirstname,
                            as: 'definedFirstName',
                            attributes: ['name']
                        },
                        {
                            model: Knowledge,
                            as: 'knowledges',
                            attributes: ['knowledge'],
                            include: [
                                {
                                    model: ProductType,
                                    as: 'productType',
                                    attributes: ['id', 'labelTr']
                                }
                            ]
                        }
                    ]
                },
            ]
        });
        return children.map(rel => ({
            id: rel.child.id,
            name: rel.child.definedFirstName.name,
            age: calcAge(rel.child.birthdate),
            knowledge: rel.child.knowledges.map(k => ({
                id: k.productType.id,
                tr: k.productType.labelTr,
                knowledge: k.knowledge
            }))
        }));
    }

    async sendGift(hashedUserId, giftId) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        const lowestTitle = await TitleOfNobility.findOne({ order: [['id', 'ASC']] });
        const currentMoodId = user.character.moodId;
        if (currentMoodId == null) {
            throw new Error('moodNotSet');
        }
        const relation = await Relationship.findOne({
            where: { character1Id: user.character.id },
            include: [{
                model: RelationshipType,
                as: 'relationshipType',
                where: { tr: 'wooing' }
            }]
        });
        if (!relation) {
            throw new Error('notRelated');
        }
        const lastGift = await PromotionalGiftLog.findOne({
            where: { senderCharacterId: user.character.id },
            order: [['createdAt', 'DESC']],
            limit: 1
        });
        if (lastGift && (lastGift.createdAt.getTime() + 3_600_000) > Date.now()) {
            throw new PreconditionError('tooOften');
        }
        const gift = await PromotionalGift.findOne({
            where: { id: giftId },
            include: [
                {
                    model: PromotionalGiftCharacterTrait,
                    as: 'characterTraits',
                    where: { trait_id: { [Op.in]: user.character.traits.map(t => t.id) } },
                    required: false
                },
                {
                    model: PromotionalGiftMood,
                    as: 'promotionalgiftmoods',
                    where: { mood_id: currentMoodId },
                    required: false
                }
            ]
        });
        if (!gift) {
            throw new Error('notFound');
        }
        const cost = await this.getGiftCost(
            gift.value,
            user.character.nobleTitle.id,
            lowestTitle.id
        );
        if (user.money < cost) {
            throw new PreconditionError('insufficientFunds');
        }
        const traits = gift.characterTraits;
        if (!traits.length) {
            throw new Error('noTraits');
        }
        // Finde den höchsten Charakterwert (wie im Frontend)
        const highestCharacterValue = Math.max(...traits.map(ct => ct.suitability));
        const moodRecord = gift.promotionalgiftmoods[0];
        if (!moodRecord) {
            throw new Error('noMoodData');
        }
        const moodSuitability = moodRecord.suitability;
        // Gleiche Berechnung wie im Frontend: (moodValue + highestCharacterValue) / 2
        const changeValue = Math.round((moodSuitability + highestCharacterValue) / 2);
        await updateFalukantUserMoney(user.id, -cost, 'Gift cost', user.id);
        await relation.update({ nextStepProgress: relation.nextStepProgress + changeValue });
        await PromotionalGiftLog.create({
            senderCharacterId: user.character.id,
            recipientCharacterId: relation.character2Id,
            giftId,
            changeValue
        });
        this.checkProposalProgress(relation);
        return { success: true, message: 'sent' };
    }

    async checkProposalProgress(relation) {
        const { nextStepProgress } = relation;
        if (nextStepProgress >= 100) {
            const engagedStatus = await RelationshipType.findOne({ where: { tr: 'engaged' } });
            await relation.update({ nextStepProgress: 0, relationshipTypeId: engagedStatus.id });
            const user = await User.findOne({
                include: [{
                    model: FalukantUser,
                    as: 'falukantData',
                    include: [{
                        model: FalukantCharacter,
                        as: 'character',
                        where: { id: relation.character1Id }
                    }]
                }]
            });
            await notifyUser(user.hashedId, 'familychanged');
        }
    }

    async getGiftCost(value, titleOfNobility, lowestTitleOfNobility) {
        const titleLevel = titleOfNobility - lowestTitleOfNobility + 1;
        return Math.round(value * Math.pow(1 + titleLevel * 0.3, 1.3) * 100) / 100;
    }

    async getTitlesOfNobility() {
        return TitleOfNobility.findAll();
    }

    async getHouseTypes() {
        //        return House
    }

    async getMoodAffect() {
        return PromotionalGiftMood.findAll();
    }

    async getCharacterAffect() {
        return PromotionalGiftCharacterTrait.findAll();
    }

    async getUserHouse(hashedUserId) {
        try {
            const user = await User.findOne({
                where: { hashedId: hashedUserId },
                include: [{
                    model: FalukantUser,
                    as: 'falukantData',
                    include: [{
                        model: UserHouse,
                        as: 'userHouse',
                        include: [{
                            model: HouseType,
                            as: 'houseType',
                            attributes: ['position', 'cost']
                        }],
                        attributes: ['roofCondition', 'wallCondition', 'floorCondition', 'windowCondition']
                    }],
                }
                ]
            });
            return user.falukantData[0].userHouse ?? { position: 0, roofCondition: 100, wallCondition: 100, floorCondition: 100, windowCondition: 100 };
        } catch (error) {
            console.log(error);
            return {};
        }
    }

    async getBuyableHouses(hashedUserId) {
        try {
            const user = await this.getFalukantUserByHashedId(hashedUserId);
            const houses = await BuyableHouse.findAll({
                include: [{
                    model: HouseType,
                    as: 'houseType',
                    attributes: ['position', 'cost', 'labelTr'],
                    where: {
                        minimumNobleTitle: {
                            [Op.lte]: user.character.nobleTitle.id
                        }
                    }
                }],
                attributes: ['roofCondition', 'wallCondition', 'floorCondition', 'windowCondition', 'id'],
                order: [
                    [{ model: HouseType, as: 'houseType' }, 'position', 'DESC'],
                    ['wallCondition', 'DESC'],
                    ['roofCondition', 'DESC'],
                    ['floorCondition', 'DESC'],
                    ['windowCondition', 'DESC']
                ]
            });
            return houses;
        } catch (error) {
            console.error('Fehler beim Laden der kaufbaren Häuser:', error);
            throw error;
        }
    }

    async buyUserHouse(hashedUserId, houseId) {
        try {
            const falukantUser = await getFalukantUserOrFail(hashedUserId);
            const house = await BuyableHouse.findByPk(houseId, {
                include: [{
                    model: HouseType,
                    as: 'houseType',
                }],
            });
            if (!house) {
                throw new Error('Das Haus wurde nicht gefunden.');
            }
            const housePrice = this.housePrice(house);
            const oldHouse = await UserHouse.findOne({ where: { userId: falukantUser.id } });
            if (Number(falukantUser.money) < Number(housePrice)) {
                throw new Error('notenoughmoney.');
            }
            if (oldHouse) {
                await oldHouse.destroy();
            }
            await UserHouse.create({
                userId: falukantUser.id,
                houseTypeId: house.houseTypeId,
            });
            await house.destroy();
            await updateFalukantUserMoney(falukantUser.id, -housePrice, "housebuy", falukantUser.id);
            const user = await User.findByPk(falukantUser.userId);
            notifyUser(user.hashedId, 'falukantHouseUpdate', {});
            return {};
        } catch (error) {
            console.error('Fehler beim Kaufen des Hauses:', error);
            throw error;
        }
    }

    housePrice(house) {
        const houseQuality = (house.roofCondition + house.windowCondition + house.floorCondition + house.wallCondition) / 4;
        return (house.houseType.cost / 100 * houseQuality).toFixed(2);
    }

    async getPartyTypes(hashedUserId) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);
        const engagedCount = await Relationship.count({
            include: [
                {
                    model: RelationshipType,
                    as: 'relationshipType',
                    where: { tr: 'engaged' },
                    required: true
                },
                {
                    model: FalukantCharacter,
                    as: 'character1',
                    where: { userId: falukantUser.id },
                    required: false
                },
                {
                    model: FalukantCharacter,
                    as: 'character2',
                    where: { userId: falukantUser.id },
                    required: false
                }
            ],
            where: {
                [Op.or]: [
                    { '$character1.user_id$': falukantUser.id },
                    { '$character2.user_id$': falukantUser.id }
                ]
            }
        });
        const orConditions = [{ forMarriage: false }];
        if (engagedCount > 0) {
            orConditions.push({ forMarriage: true });
        }
        const partyTypes = await PartyType.findAll({
            where: {
                [Op.or]: orConditions
            },
            order: [['cost', 'ASC']]
        });
        const musicTypes = await MusicType.findAll();
        const banquetteTypes = await BanquetteType.findAll();
        return { partyTypes, musicTypes, banquetteTypes };
    }

    async createParty(hashedUserId, partyTypeId, musicId, banquetteId, nobilityIds = [], servantRatio) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);
        const since = new Date(Date.now() - 24 * 3600 * 1000);
        const already = await Party.findOne({
            where: {
                falukantUserId: falukantUser.id,
                partyTypeId,
                createdAt: { [Op.gte]: since },
            },
            attributes: ['id']
        });
        if (already) {
            throw new Error('Diese Party wurde bereits innerhalb der letzten 24 Stunden bestellt');
        }
        const [ptype, music, banquette] = await Promise.all([
            PartyType.findByPk(partyTypeId),
            MusicType.findByPk(musicId),
            BanquetteType.findByPk(banquetteId),
        ]);
        if (!ptype || !music || !banquette) {
            throw new Error('Ungültige Party-, Musik- oder Bankett-Auswahl');
        }
        const nobilities = nobilityIds.length
            ? await TitleOfNobility.findAll({ where: { id: { [Op.in]: nobilityIds } } })
            : [];
        let cost = (ptype.cost || 0) + (music.cost || 0) + (banquette.cost || 0);
        cost += (50 / servantRatio - 1) * 1000;
        const nobilityCost = nobilities.reduce((sum, n) => sum + ((n.id ^ 5) * 1000), 0);
        cost += nobilityCost;
        if (Number(falukantUser.money) < cost) {
            throw new Error('Nicht genügend Guthaben für diese Party');
        }
        const moneyResult = await updateFalukantUserMoney(
            falukantUser.id,
            -cost,
            'partyOrder',
            falukantUser.id
        );
        if (!moneyResult.success) {
            throw new Error('Geld konnte nicht abgezogen werden');
        }
        const party = await Party.create({
            partyTypeId,
            falukantUserId: falukantUser.id,
            musicTypeId: musicId,
            banquetteTypeId: banquetteId,
            servantRatio,
            cost: cost
        });
        if (nobilityIds.length) {
            await party.addInvitedNobilities(nobilityIds);
        }
        const user = await User.findByPk(falukantUser.userId);
        notifyUser(user.hashedId, 'falukantPartyUpdate', {
            partyId: party.id,
            cost,
        });
        return { 'success': true };
    }

    async getParties(hashedUserId) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);
        const parties = await Party.findAll({
            where: { falukantUserId: falukantUser.id },
            include: [
                {
                    model: PartyType,
                    as: 'partyType',
                    attributes: ['tr'],
                },
                {
                    model: MusicType,
                    as: 'musicType',
                    attributes: ['tr'],
                },
                {
                    model: BanquetteType,
                    as: 'banquetteType',
                    attributes: ['tr'],
                },
            ],
            order: [['createdAt', 'DESC']],
            attributes: ['id', 'createdAt', 'servantRatio', 'cost'],
        });
        return parties;
    }

    async getNotBaptisedChildren(hashedUserId) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);
        const children = await ChildRelation.findAll({
            include: [
                {
                    model: FalukantCharacter,
                    as: 'father',
                    where: {
                        userId: falukantUser.id,
                    },
                    required: false,
                },
                {
                    model: FalukantCharacter,
                    as: 'mother',
                    where: {
                        userId: falukantUser.id,
                    },
                    required: false,
                },
                {
                    model: FalukantCharacter,
                    as: 'child',
                    required: true,
                    include: [
                        {
                            model: FalukantPredefineFirstname,
                            as: 'definedFirstName',
                            required: true,
                        },
                    ]
                },
            ],
            where: {
                nameSet: false,
            },
            order: [['createdAt', 'DESC']],
        });
        return children.map(child => {
            return {
                id: child.child.id,
                gender: child.child.gender,
                age: calcAge(child.child.birthdate),
                proposedFirstName: child.child.definedFirstName.name,
            };
        });
    }

    async baptise(hashedUserId, childId, firstName) {
        try {
            const falukantUser = await getFalukantUserOrFail(hashedUserId);
            const parentCharacter = await FalukantCharacter.findOne({
                where: {
                    userId: falukantUser.id,
                },
            });
            if (!parentCharacter) {
                throw new Error('Parent character not found');
            }
            const child = await FalukantCharacter.findOne({
                where: {
                    id: childId,
                },
            });
            if (!child) {
                throw new Error('Child not found');
            }
            const childRelation = await ChildRelation.findOne({
                where: {
                    [Op.or]: [
                        {
                            fatherCharacterId: parentCharacter.id,
                            childCharacterId: child.id,
                        },
                        {
                            motherCharacterId: parentCharacter.id,
                            childCharacterId: child.id,
                        }
                    ]
                }
            });
            if (!childRelation) {
                throw new Error('Child relation not found');
            }
            await childRelation.update({
                nameSet: true,
            });
            let firstNameObject = await FalukantPredefineFirstname.findOne({
                where: {
                    name: firstName,
                    gender: child.gender,
                },
            });
            if (!firstNameObject) {
                firstNameObject = await FalukantPredefineFirstname.create({
                    name: firstName,
                    gender: child.gender,
                });
            }
            await child.update({
                firstName: firstNameObject.id,
            });
            updateFalukantUserMoney(falukantUser.id, -50, 'Baptism', falukantUser.id);
            // Trigger status bar refresh for the user after baptism
            notifyUser(hashedUserId, 'falukantUpdateStatus', {});
            return { success: true };
        } catch (error) {
            throw new Error(error.message);
        }
    }

    async getEducation(hashedUserId) {
        try {
            const falukantUser = await getFalukantUserOrFail(hashedUserId);
            const education = await Learning.findAll({
                where: {
                    createdAt: { [Op.gt]: new Date().getTime() - 1000 * 60 * 60 * 24 },
                },
                include: [
                    {
                        model: LearnRecipient,
                        as: 'recipient',
                        attributes: ['tr']
                    },
                    {
                        model: ProductType,
                        as: 'productType',
                        attributes: ['labelTr']
                    },
                    {
                        model: FalukantUser,
                        as: 'learner',
                        where: {
                            id: falukantUser.id
                        },
                        attributes: []
                    },
                    {
                        model: FalukantCharacter,
                        as: 'learningCharacter',
                        attributes: ['id']
                    }
                ],
                attributes: ['createdAt'],
            });
            return education;
        } catch (error) {
            console.log(error);
            throw new Error(error.message);
        }
    }

    computeCost(percent, mode) {
        const cfg = FalukantService.COST_CONFIG[mode];
        // clamp percent auf [0, KNOWLEDGE_MAX]
        const p = Math.min(Math.max(percent, 0), FalukantService.KNOWLEDGE_MAX)
            / FalukantService.KNOWLEDGE_MAX;
        return Math.round(cfg.min + (cfg.max - cfg.min) * p);
    }

    async sendToSchool(hashedUserId, item, student, studentId) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);

        // 1) prüfen, ob schon in Arbeit
        const education = await this.getEducation(hashedUserId);
        const already = education.some(e =>
            e.recipient.tr === student &&
            (studentId == null || e.learningCharacter?.id === studentId)
        );
        if (already) throw new Error('Already learning this character');

        // 2) Empfänger holen
        const rec = await LearnRecipient.findOne({ where: { tr: student } });
        if (!rec) throw new Error('Character not found');

        // 3) Wissens-Prozentsatz ermitteln
        let percent;
        if (item === 'all') {
            const all = await this.getKnowledgeForAll(hashedUserId, student, studentId);
            const sum = all.reduce((s, k) => s + k.knowledge, 0);
            percent = sum / all.length;
        } else {
            const single = await this.getKnowledgeSingle(hashedUserId, student, studentId, item);
            percent = single.knowledge;
        }

        // 4) Kosten berechnen
        const mode = item === 'all' ? 'all' : 'one';
        const cost = this.computeCost(percent, mode);

        // 5) Kontostand prüfen
        if (parseFloat(falukantUser.money) < cost) {
            throw new Error('Not enough money');
        }

        // 6) Learning anlegen
        await Learning.create({
            learningRecipientId: rec.id,
            associatedLearningCharacterId: studentId,
            associatedFalukantUserId: falukantUser.id,
            productId: item === 'all' ? null : item,
            learnAllProducts: (item === 'all')
        });

        // 7) Geld abziehen
        const upd = await updateFalukantUserMoney(
            falukantUser.id,
            -cost,
            item === 'all' ? 'learnAll' : `learnItem:${item}`,
            falukantUser.id
        );
        if (!upd.success) throw new Error(upd.message);

        return true;
    }

    async getBankOverview(hashedUserId) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);
        if (!falukantUser) throw new Error('User not found');

        // 1) offene Schulden
        const totalDebt = await Credit.sum('remaining_amount', {
            where: { falukant_user_id: falukantUser.id }
        }) || 0;

        // 2) Häuser ermitteln
        const userHouses = await UserHouse.findAll({
            where: { userId: falukantUser.id },
            include: [{ model: HouseType, as: 'houseType', attributes: ['cost'] }]
        });

        // 3) Hauswert berechnen: buyCost * 0.8
        let houseValue = 0;
        for (const uh of userHouses) {
            const { roofCondition, wallCondition, floorCondition, windowCondition } = uh;
            const qualityAvg = (roofCondition + wallCondition + floorCondition + windowCondition) / 4;
            const buyWorth = (uh.houseType.cost / 100) * qualityAvg;
            houseValue += buyWorth * 0.8;
        }

        // 4) Filialwert (1000 pro Branch)
        const branchCount = await Branch.count({ where: { falukantUserId: falukantUser.id } });
        const branchValue = branchCount * 1000;

        // 5) Maximaler Kredit und verfügbare Linie
        const maxCredit = Math.floor(houseValue + branchValue);
        const availableCredit = maxCredit - totalDebt;

        // 6) aktive Kredite laden
        const activeCredits = await Credit.findAll({
            where: { falukantUserId: falukantUser.id },
            attributes: ['id', 'amount', 'remainingAmount', 'interestRate']
        });

        return {
            money: falukantUser.money,
            totalDebt,
            maxCredit,
            availableCredit,
            activeCredits,
            fee: 7
        };
    }

    async getBankCredits(hashedUserId) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);
        if (!falukantUser) throw new Error('User not found');
        const credits = await Credit.findAll({
            where: { falukantUserId: falukantUser.id },
        });
        return credits;
    }

    async takeBankCredits(hashedUserId, height) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);
        if (!falukantUser) throw new Error('User not found');
        const financialData = await this.getBankOverview(hashedUserId);
        if (financialData.availableCredit < height) {
            throw new Error('Not enough credit');
        }
        const newCredit = await Credit.create({
            falukantUserId: falukantUser.id,
            amount: height,
            remainingAmount: height,
            interestRate: financialData.fee,
        });
        updateFalukantUserMoney(falukantUser.id, height, 'credit taken', falukantUser.id);
        return { height: newCredit.amount, fee: newCredit.interestRate };
    }

    async getNobility(hashedUserId) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);
        const nobility = await TitleOfNobility.findOne({
            include: [
                {
                    model: FalukantCharacter,
                    as: 'charactersWithNobleTitle',
                    attributes: ['gender'],
                    where: {
                        userId: falukantUser.id
                    }
                },
                {
                    model: TitleRequirement,
                    as: 'requirements',
                    attributes: ['requirementType', 'requirementValue']
                }
            ],
            attributes: ['labelTr', 'level']
        });
        const currentTitleLevel = nobility.level;
        const nextTitle = await TitleOfNobility.findOne({
            where: {
                level: currentTitleLevel + 1
            },
            include: [
                {
                    model: TitleRequirement,
                    as: 'requirements',
                }
            ],
            attributes: ['labelTr']
        });
        return {
            current: nobility,
            next: nextTitle
        };
    }

    async advanceNobility(hashedUserId) {
        const nobility = await this.getNobility(hashedUserId);
        if (!nobility || !nobility.next) {
            throw new Error('User does not have a nobility');
        }
        const nextTitle = nobility.next.toJSON();
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        let fulfilled = true;
        let cost = 0;
        for (const requirement of nextTitle.requirements) {
            switch (requirement.requirementType) {
                case 'money':
                    fulfilled = fulfilled && await this.checkMoneyRequirement(user, requirement);
                    break;
                case 'cost':
                    fulfilled = fulfilled && await this.checkMoneyRequirement(user, requirement);
                    cost = requirement.requirementValue;
                    break;
                case 'branches':
                    fulfilled = fulfilled && await this.checkBranchesRequirement(hashedUserId, requirement);
                    break;
                default:
                    fulfilled = false;
            };
        }
        if (!fulfilled) {
            throw new Error('Requirements not fulfilled');
        }
        const newTitle = await TitleOfNobility.findOne({
            where: { level: nobility.current.level + 1 }
        });
        const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
        await character.update({ titleOfNobility: newTitle.id });
        if (cost > 0) {
            updateFalukantUserMoney(user.id, -cost, 'new nobility title', user.id);
        }
        return {};
    }

    async checkMoneyRequirement(user, requirement) {
        return user.money >= requirement.requirementValue;
    }

    async checkBranchesRequirement(hashedUserId, requirement) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
    }

    async getHealth(hashedUserId) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        const healthActivities = FalukantService.HEALTH_ACTIVITIES.map((activity) => { return { tr: activity.tr, cost: activity.cost } });
        const healthHistory = await HealthActivity.findAll({
            where: { characterId: user.character.id },
            order: [['createdAt', 'DESC']],
        });
        return {
            age: calcAge(user.character.birthdate),
            health: user.character.health,
            healthActivities: healthActivities,
            history: healthHistory.map((activity) => { return { tr: activity.activityTr, cost: activity.cost, createdAt: activity.createdAt, success: activity.successPercentage } }),
        };
    }

    async healthActivity(hashedUserId, activity) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        const lastHealthActivity = await HealthActivity.findOne({
            where: {
                characterId: user.character.id,
                activityTr: activity,
                createdAt: {
                    [Op.gte]: new Date(new Date().setDate(new Date().getDate() - 1))
                }
            },
            order: [['createdAt', 'DESC']],
            limit: 1
        });
        if (lastHealthActivity) {
            throw new Error('too close');
        }
        const activityObject = FalukantService.HEALTH_ACTIVITIES.find((a) => a.tr === activity);
        if (!activityObject) {
            throw new Error('invalid');
        }
        if (user.money - activityObject.cost < 0) {
            throw new Error('no money');
        }
        user.character.health -= activityObject.cost;
        await HealthActivity.create({
            characterId: user.character.id,
            activityTr: activity,
            successPercentage: await this[activityObject.method](user),
            cost: activityObject.cost
        });
        updateFalukantUserMoney(user.id, -activityObject.cost, 'health.' + activity);
        return { success: true };
    }

    async healthChange(user, delta) {
        const char = await FalukantCharacter.findOne({
            where: {
                id: user.character.id
            }
        });
        await char.update({
            health: Math.min(FalukantService.HEALTH_MAX || 100, Math.max(0, char.health + delta))
        });
        return delta;
    }

    async healthBarber(user) {
        const raw = Math.floor(Math.random() * 11) - 5;
        return this.healthChange(user, raw);
    }

    async healthDoctor(user) {
        const raw = Math.floor(Math.random() * 8) - 2;
        return this.healthChange(user, raw);
    }

    async healthWitch(user) {
        const raw = Math.floor(Math.random() * 7) - 1;
        return this.healthChange(user, raw);
    }

    async healthPill(user) {
        const raw = Math.floor(Math.random() * 8);
        return this.healthChange(user, raw);
    }

    async healthDrunkOfLife(user) {
        const raw = Math.floor(Math.random() * 26);
        return this.healthChange(user, raw);
    }

    async getPoliticsOverview(hashedUserId) {

    }

    async getOpenPolitics(hashedUserId) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        if (!user || user.character.nobleTitle.labelTr === 'noncivil') {
            return [];
        }

    }

    async getElections(hashedUserId) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        if (!user || user.character.nobleTitle.labelTr === 'noncivil') {
            return [];
        }
        const rows = await sequelize.query(
            FalukantService.RECURSIVE_REGION_SEARCH,
            {
                replacements: { user_id: user.id },
                type: sequelize.QueryTypes.SELECT
            }
        );
        const regionIds = rows.map(r => r.id);

        // 3) Zeitbereich "heute"
        const todayStart = new Date();
        todayStart.setHours(0, 0, 0, 0);
        const todayEnd = new Date();
        todayEnd.setHours(23, 59, 59, 999);

        // 4) Wahlen laden (inkl. Kandidaten, Stimmen und Verknüpfungen)
        const rawElections = await Election.findAll({
            where: {
                regionId: { [Op.in]: regionIds },
                date: { [Op.between]: [todayStart, todayEnd] }
            },
            include: [
                {
                    model: RegionData,
                    as: 'region',
                    attributes: ['name'],
                    include: [{
                        model: RegionType,
                        as: 'regionType',
                        attributes: ['labelTr']
                    }]
                },
                {
                    model: PoliticalOfficeType,
                    as: 'officeType',
                    attributes: ['name']
                },
                {
                    model: Candidate,
                    as: 'candidates',
                    attributes: ['id'],
                    include: [{
                        model: FalukantCharacter,
                        as: 'character',
                        attributes: ['birthdate', 'gender'],
                        include: [
                            { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
                            { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
                            { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }
                        ]
                    }]
                },
                {
                    model: Vote,
                    as: 'votes',
                    attributes: ['candidateId'],
                    where: {
                        falukantUserId: user.id
                    },
                    required: false
                }
            ]
        });

        return rawElections.map(election => {
            const e = election.get({ plain: true });

            const voted = Array.isArray(e.votes) && e.votes.length > 0;
            const reducedCandidates = (e.candidates || []).map(cand => {
                const ch = cand.character || {};
                const firstname = ch.definedFirstName?.name || '';
                const lastname = ch.definedLastName?.name || '';
                return {
                    id: cand.id,
                    title: ch.nobleTitle?.labelTr || null,
                    name: `${firstname} ${lastname}`.trim(),
                    age: calcAge(ch.birthdate),
                    gender: ch.gender
                };
            });

            return {
                id: e.id,
                officeType: { name: e.officeType.name },
                region: {
                    name: e.region.name,
                    regionType: { labelTr: e.region.regionType.labelTr }
                },
                date: e.date,
                postsToFill: e.postsToFill,
                candidates: reducedCandidates,
                voted: voted,
                votedFor: voted ? e.votes.map(vote => { return vote.candidateId }) : null,
            };
        });
    }

    async vote(hashedUserId, votes) {
        const elections = await this.getElections(hashedUserId);
        if (!Array.isArray(elections) || elections.length === 0) {
            throw new Error('No elections found');
        }
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        if (!user) {
            throw new Error('User not found');
        }
        const validElections = votes.filter(voteEntry => {
            const e = elections.find(el => el.id === voteEntry.electionId);
            return e && !e.voted;
        });

        if (validElections.length === 0) {
            throw new Error('No valid elections to vote for (either non‐existent or already voted)');
        }
        validElections.forEach(voteEntry => {
            const e = elections.find(el => el.id === voteEntry.electionId);
            const allowedIds = e.candidates.map(c => c.id);
            voteEntry.candidateIds.forEach(cid => {
                if (!allowedIds.includes(cid)) {
                    throw new Error(`Candidate ID ${cid} is not valid for election ${e.id}`);
                }
            });
            if (voteEntry.candidateIds.length > e.postsToFill) {
                throw new Error(`Too many candidates selected for election ${e.id}. Allowed: ${e.postsToFill}`);
            }
        });
        return await sequelize.transaction(async (tx) => {
            const toCreate = [];
            validElections.forEach(voteEntry => {
                voteEntry.candidateIds.forEach(candidateId => {
                    toCreate.push({
                        electionId: voteEntry.electionId,
                        candidateId,
                        falukantUserId: user.id
                    });
                });
            });
            await Vote.bulkCreate(toCreate, {
                transaction: tx,
                ignoreDuplicates: true,
                returning: false
            });
            return { success: true };
        });
    }

    async getOpenPolitics(hashedUserId) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        const characterId = user.character.id;
        const rows = await sequelize.query(
            FalukantService.RECURSIVE_REGION_SEARCH,
            {
                replacements: { user_id: user.id },
                type: sequelize.QueryTypes.SELECT
            }
        );
        const regionIds = rows.map(r => r.id);
        const histories = await PoliticalOfficeHistory.findAll({
            where: { characterId },
            attributes: ['officeTypeId', 'startDate', 'endDate']
        });
        const heldOfficeTypeIds = histories.map(h => h.officeTypeId);
        const allTypes = await PoliticalOfficeType.findAll({ attributes: ['id', 'name'] });
        const nameToId = Object.fromEntries(allTypes.map(t => [t.name, t.id]));
        const openPositions = await Election.findAll({
            where: {
                regionId: { [Op.in]: regionIds },
                date: { [Op.lt]: new Date() }
            },
            include: [
                {
                    model: RegionData,
                    as: 'region',
                    attributes: ['name'],
                    include: [
                        { model: RegionType, as: 'regionType', attributes: ['labelTr'] }
                    ]
                },
                { model: Candidate, as: 'candidates' },
                {
                    model: PoliticalOfficeType, as: 'officeType',
                    include: [{ model: PoliticalOfficePrerequisite, as: 'prerequisites' }]
                }
            ]
        });
        const result = openPositions
            .filter(election => {
                const prereqs = election.officeType.prerequisites || [];
                return prereqs.some(pr => {
                    const jobs = pr.prerequisite.jobs;
                    if (!Array.isArray(jobs) || jobs.length === 0) return true;
                    return jobs.some(jobName => {
                        const reqId = nameToId[jobName];
                        return heldOfficeTypeIds.includes(reqId);
                    });
                });
            })
            .map(election => {
                const e = election.get({ plain: true });
                const jobs = e.officeType.prerequisites[0]?.prerequisite.jobs || [];
                const matchingHistory = histories
                    .filter(h => jobs.includes(allTypes.find(t => t.id === h.officeTypeId)?.name))
                    .map(h => ({
                        officeTypeId: h.officeTypeId,
                        startDate: h.startDate,
                        endDate: h.endDate
                    }));

                return {
                    ...e,
                    history: matchingHistory
                };
            });
        return result;
    }

    async applyForElections(hashedUserId, electionIds) {
        // 1) Hole FalukantUser + Character
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        if (!user) {
            throw new Error('User nicht gefunden');
        }
        const character = user.character;
        if (!character) {
            throw new Error('Kein Charakter zum User gefunden');
        }

        // 2) Noncivil‐Titel aussperren
        if (character.nobleTitle.labelTr === 'noncivil') {
            return { applied: [], skipped: electionIds };
        }

        // 3) Ermittle die heute offenen Wahlen, auf die er zugreifen darf
        //    (getElections liefert id, officeType, region, date, postsToFill, candidates, voted…)
        const openElections = await this.getElections(hashedUserId);
        const allowedIds = new Set(openElections.map(e => e.id));

        // 4) Filter alle electionIds auf gültige/erlaubte
        const toTry = electionIds.filter(id => allowedIds.has(id));
        if (toTry.length === 0) {
            return { applied: [], skipped: electionIds };
        }

        // 5) Prüfe, auf welche dieser Wahlen der Character bereits als Candidate eingetragen ist
        const existing = await Candidate.findAll({
            where: {
                electionId: { [Op.in]: toTry },
                characterId: character.id
            },
            attributes: ['electionId']
        });
        const alreadyIds = new Set(existing.map(c => c.electionId));

        // 6) Erstelle Liste der Wahlen, für die er sich noch nicht beworben hat
        const newApplications = toTry.filter(id => !alreadyIds.has(id));
        const skipped = electionIds.filter(id => !newApplications.includes(id));

        console.log(newApplications, skipped);

        // 7) Bulk-Insert aller neuen Bewerbungen
        if (newApplications.length > 0) {
            const toInsert = newApplications.map(eid => ({
                electionId: eid,
                characterId: character.id
            }));
            await Candidate.bulkCreate(toInsert);
        }

        return {
            applied: newApplications,
            skipped: skipped
        };
    }

    async getRegions(hashedUserId) {
        const user = await this.getFalukantUserByHashedId(hashedUserId);
        const regions = await RegionData.findAll({
            attributes: ['id', 'name', 'map'],
            include: [
                {
                    model: RegionType,
                    as: 'regionType',
                    where: {
                        labelTr: 'city'
                    },
                    attributes: ['labelTr']
                },
                {
                    model: Branch,
                    as: 'branches',
                    where: {
                        falukantUserId: user.id
                    },
                    include: [
                        {
                            model: BranchType,
                            as: 'branchType',
                            attributes: ['labelTr'],
                        },
                    ],
                    attributes: ['branchTypeId'],
                    required: false,
                }
            ]
        });
        return regions;
    }

    async renovate(hashedUserId, element) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const house = await UserHouse.findOne({
            where: { userId: user.id },
            include: [{ model: HouseType, as: 'houseType' }]
        });
        if (!house) throw new Error('House not found');
        const oldValue = house[element];
        if (oldValue >= 100) {
            return { cost: 0 };
        }
        const baseCost = house.houseType?.cost || 0;
        const cost = this._calculateRenovationCost(baseCost, element, oldValue);
        house[element] = 100;
        await house.save();
        await updateFalukantUserMoney(
            user.id,
            -cost,
            `renovation_${element}`
        );
        return { cost };
    }

    _calculateRenovationCost(baseCost, key, currentVal) {
        const weights = {
            roofCondition: 0.25,
            wallCondition: 0.25,
            floorCondition: 0.25,
            windowCondition: 0.25
        };
        const weight = weights[key] || 0;
        const missing = 100 - currentVal;
        const raw = (missing / 100) * baseCost * weight;
        return Math.round(raw * 100) / 100;
    }

    async renovateAll(hashedUserId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const house = await UserHouse.findOne({
            where: { userId: user.id },
            include: [{ model: HouseType, as: 'houseType' }]
        });
        if (!house) throw new Error('House not found');
        const baseCost = house.houseType?.cost || 0;
        const keys = ['roofCondition', 'wallCondition', 'floorCondition', 'windowCondition'];
        let rawSum = 0;
        for (const key of keys) {
            const current = house[key];
            if (current < 100) {
                rawSum += this._calculateRenovationCost(baseCost, key, current);
            }
        }
        const totalCost = Math.round(rawSum * 0.8 * 100) / 100;
        for (const key of keys) {
            house[key] = 100;
        }
        await house.save();
        await updateFalukantUserMoney(
            user.id,
            -totalCost,
            'renovation_all'
        );

        return { cost: totalCost };
    }

    async getUndergroundTypes(hashedUserId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const undergroundTypes = await UndergroundType.findAll();
        return undergroundTypes;
    }

    async getNotifications(hashedUserId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const notifications = await Notification.findAll({
            where: { userId: user.id, shown: false },
            order: [['createdAt', 'DESC']]
        });
        return notifications;
    }

    async getAllNotifications(hashedUserId, page = 1, size = 10) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const limit = Math.max(1, Math.min(Number(size) || 10, 100));
        const offset = Math.max(0, ((Number(page) || 1) - 1) * limit);
        const { rows, count } = await Notification.findAndCountAll({
            where: { userId: user.id },
            order: [['createdAt', 'DESC']],
            offset,
            limit,
        });
        return { items: rows, total: count, page: Number(page) || 1, size: limit };
    }

    async markNotificationsShown(hashedUserId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const [count] = await Notification.update(
            { shown: true },
            { where: { userId: user.id, shown: false } }
        );
        return { updated: count };
    }

    async getPoliticalOfficeHolders(hashedUserId) {
        const user = await getFalukantUserOrFail(hashedUserId);
        const character = await FalukantCharacter.findOne({
            where: {
                userId: user.id
            }
        });
        const relevantRegionIds = await this.getRegionAndParentIds(character.regionId);
        const now = new Date();
        const histories = await PoliticalOffice.findAll({
            where: {
                regionId: {
                    [Op.in]: relevantRegionIds
                },
            },
            include: [{
                model: FalukantCharacter,
                as: 'holder',
                include: [
                    { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
                    { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
                    { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }
                ],
                attributes: ['id', 'gender']
            }, {
                model: PoliticalOfficeType,
                as: 'type',
            }]
        });

        // Unikate nach character.id
        const map = new Map();
        histories.forEach(h => {
            const c = h.holder;
            if (c && c.id && !map.has(c.id)) {
                map.set(c.id, {
                    id: c.id,
                    name: `${c.definedFirstName.name} ${c.definedLastName.name}`,
                    title: c.nobleTitle.labelTr,
                    officeType: h.type.name,
                    gender: c.gender
                });
            }
        });

        return Array.from(map.values());
    }

    async getRegionAndParentIds(regionId) {
        const relevantRegionIds = new Set();
        let currentRegionId = regionId;

        while (currentRegionId !== null) {
            relevantRegionIds.add(currentRegionId);
            const region = await RegionData.findByPk(currentRegionId, {
                attributes: ['parentId']
            });

            if (region && region.parentId) {
                currentRegionId = region.parentId;
            } else {
                currentRegionId = null; // Keine weitere Elternregion gefunden
            }
        }
        return Array.from(relevantRegionIds);
    }

    // vorher: async searchUsers(q) {
    async searchUsers(hashedUserId, q) {
        // User-Prüfung wie bei anderen Methoden
        await getFalukantUserOrFail(hashedUserId); // wir brauchen das Ergebnis hier nicht weiter, nur Validierung

        const chars = await FalukantCharacter.findAll({
            where: {
                userId: { [Op.ne]: null },
                [Op.or]: [
                    { '$user.user.username$': { [Op.iLike]: `%${q}%` } },
                    { '$definedFirstName.name$': { [Op.iLike]: `%${q}%` } },
                    { '$definedLastName.name$': { [Op.iLike]: `%${q}%` } }
                ]
            },
            include: [
                {
                    model: FalukantUser,
                    as: 'user',
                    include: [
                        {
                            model: User,
                            as: 'user',
                            attributes: ['username']
                        }
                    ]
                },
                {
                    model: FalukantPredefineFirstname,
                    as: 'definedFirstName',
                    attributes: ['name']
                },
                {
                    model: FalukantPredefineLastname,
                    as: 'definedLastName',
                    attributes: ['name']
                },
                {
                    model: RegionData,
                    as: 'region',
                    attributes: ['name']
                }
            ],
            limit: 50,
            raw: false
        });

        // Debug-Log (optional)
        console.log('FalukantService.searchUsers raw result for', q, chars);

        const mapped = chars
            .map(c => ({
                username: c.user?.user?.username || null,
                firstname: c.definedFirstName?.name || null,
                lastname: c.definedLastName?.name || null,
                town: c.region?.name || null
            }))
            .filter(u => u.username);

        console.log('FalukantService.searchUsers mapped result for', q, mapped);
        return mapped;
    }

    async createUndergroundActivity(hashedUserId, payload) {
        const { typeId, victimUsername, target, goal, politicalTargets } = payload;

        // 1) Performer auflösen
        const falukantUser = await this.getFalukantUserByHashedId(hashedUserId);
        if (!falukantUser || !falukantUser.character) {
            throw new Error('Performer not found');
        }
        const performerChar = falukantUser.character;

        // 2) Victim auflösen über Username (inner join)
        const victimChar = await FalukantCharacter.findOne({
            include: [
                {
                    model: FalukantUser,
                    as: 'user',
                    required: true, // inner join
                    attributes: [],
                    include: [
                        {
                            model: User,
                            as: 'user',
                            required: true, // inner join
                            where: { username: victimUsername },
                            attributes: []
                        }
                    ]
                }
            ]
        });

        if (!victimChar) {
            throw new PreconditionError('Victim character not found');
        }

        // 3) Selbstangriff verhindern
        if (victimChar.id === performerChar.id) {
            throw new PreconditionError('Cannot target yourself');
        }

        // 4) Typ-spezifische Validierung
        const undergroundType = await UndergroundType.findByPk(typeId);
        if (!undergroundType) {
            throw new Error('Invalid underground type');
        }

        if (undergroundType.tr === 'sabotage') {
            if (!target) {
                throw new PreconditionError('Sabotage target missing');
            }
        }

        if (undergroundType.tr === 'corrupt_politician') {
            if (!goal) {
                throw new PreconditionError('Corrupt goal missing');
            }
            // politicalTargets kann optional sein, falls benötigt prüfen
        }

        // 5) Eintrag anlegen (optional: in Transaction)
        const newEntry = await Underground.create({
            undergroundTypeId: typeId,
            performerId: performerChar.id,
            victimId: victimChar.id,
            result: null,
            parameters: {
                target: target || null,
                goal: goal || null,
                politicalTargets: politicalTargets || null
            }
        });

        return newEntry;
    }


    async getUndergroundAttacks(hashedUserId) {
        const falukantUser = await getFalukantUserOrFail(hashedUserId);
        const character = await FalukantCharacter.findOne({
            where: { userId: falukantUser.id }
        });
        if (!character) throw new Error('Character not found');

        const charId = character.id;

        const attacks = await Underground.findAll({
            where: {
                [Op.or]: [
                    { performerId: charId },
                    { victimId: charId }
                ]
            },
            include: [
                {
                    model: FalukantCharacter,
                    as: 'performer',
                    include: [
                        { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
                        { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
                        {
                            model: FalukantUser,
                            as: 'user',
                            include: [{ model: User, as: 'user', attributes: ['username'] }],
                            attributes: []
                        }
                    ],
                    attributes: ['id', 'gender']
                },
                {
                    model: FalukantCharacter,
                    as: 'victim',
                    include: [
                        { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
                        { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
                        {
                            model: FalukantUser,
                            as: 'user',
                            include: [{ model: User, as: 'user', attributes: ['username'] }],
                            attributes: []
                        }
                    ],
                    attributes: ['id', 'gender']
                },
                {
                    model: UndergroundType,
                    as: 'undergroundType', // hier der korrekte Alias
                    attributes: ['tr']
                }
            ],
            order: [['createdAt', 'DESC']]
        });

        const formatCharacter = (c) => {
            if (!c) return null;
            return {
                id: c.id,
                username: c.user?.user?.username || null,
                firstname: c.definedFirstName?.name || null,
                lastname: c.definedLastName?.name || null,
                gender: c.gender
            };
        };

        const mapped = attacks.map(a => ({
            id: a.id,
            result: a.result,
            createdAt: a.createdAt,
            updatedAt: a.updatedAt,
            type: a.undergroundType?.tr || null, // angepasst
            parameters: a.parameters,
            performer: formatCharacter(a.performer),
            victim: formatCharacter(a.victim),
            success: !!a.result
        }));

        return {
            sent: mapped.filter(a => a.performer?.id === charId),
            received: mapped.filter(a => a.victim?.id === charId),
            all: mapped
        };
    }
}

export default new FalukantService();
