import BaseService from './BaseService.js';
import { Op } from 'sequelize';
import User from '../models/community/user.js';
import UserParam from '../models/community/user_param.js';
import UserParamType from '../models/type/user_param.js';
import UserParamValue from '../models/type/user_param_value.js';
import UserParamVisibility from '../models/community/user_param_visibility.js';
import UserParamVisibilityType from '../models/type/user_param_visibility.js';
import Folder from '../models/community/folder.js';
import Image from '../models/community/image.js';
import ImageVisibilityType from '../models/type/image_visibility.js';
import FolderImageVisibility from '../models/community/folder_image_visibility.js';
import ImageImageVisibility from '../models/community/image_image_visibility.js';
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';
import fsPromises from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import FolderVisibilityUser from '../models/community/folder_visibility_user.js';
import ImageVisibilityUser from '../models/community/image_visibility_user.js';
import GuestbookEntry from '../models/community/guestbook.js';
import { JSDOM } from 'jsdom';
import DOMPurify from 'dompurify';
import sharp from 'sharp';
import Diary from '../models/community/diary.js';
import Friendship from '../models/community/friendship.js';
import { getUserSession } from '../utils/redis.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

class SocialNetworkService extends BaseService {
    async searchUsers({ hashedUserId, username, ageFrom, ageTo, genders }) {
        const whereClause = this.buildSearchWhereClause(username);
        const user = await this.loadUserByHash(hashedUserId);
        console.log(hashedUserId, user);
        if (!user) {
            throw new Error('User not found');
        }
        whereClause.id = { [Op.ne]: user.id };
        const users = await User.findAll({ where: whereClause, include: this.getUserParamsInclude() });
        return await this.filterUsersByCriteria(users, ageFrom, ageTo, genders);
    }

    async getProfile(hashedUserId, requestingUserId) {
        await this.checkUserAccess(requestingUserId);
        const user = await this.fetchUserProfile(hashedUserId);
        if (!user) return null;
        return this.constructUserProfile(user, requestingUserId);
    }

    async createFolder(hashedUserId, data, folderId) {
        await this.checkUserAccess(hashedUserId);
        const user = await this.loadUserByHash(hashedUserId);
        if (!user) {
            throw new Error('User not found');
        }
        console.log('given data', data, folderId);
        const parentFolder = data.parentId ? await Folder.findOne({
            where: { id: data.parentId, userId: user.id }
        }) : null;
        if (data.parentId && !parentFolder) {
            throw new Error('Parent folder not found');
        }
        console.log('parentFolder', parentFolder);
        let newFolder;
        if (parentFolder) {
            newFolder = await Folder.create({
                parentId: parentFolder.id || null,
                userId: user.id,
                name: data.name
            });
        } else {
            newFolder = await Folder.findOne({
                where: { id: folderId, userId: user.id }
            });
            if (!newFolder) {
                throw new Error('Folder not found or user does not own the folder');
            }
            newFolder.name = data.name;
            await newFolder.save();
        }
        await FolderImageVisibility.destroy({
            where: { folderId: newFolder.id }
        });
        for (const visibilityId of data.visibilities) {
            await FolderImageVisibility.create({
                folderId: newFolder.id,
                visibilityTypeId: visibilityId
            });
        }
        return newFolder;
    }

    async getFolders(hashedId) {
        const userId = await this.checkUserAccess(hashedId);
        let rootFolder = await Folder.findOne({
            where: { parentId: null, userId },
            include: [{
                model: ImageVisibilityType,
                through: { model: FolderImageVisibility },
                attributes: ['id'],
            }]
        });
        if (!rootFolder) {
            const user = await User.findOne({ where: { id: userId } });
            const visibility = await ImageVisibilityType.findOne({
                where: { description: 'everyone' }
            });
            rootFolder = await Folder.create({
                name: user.username,
                parentId: null,
                userId
            });
            await FolderImageVisibility.create({
                folderId: rootFolder.id,
                visibilityTypeId: visibility.id
            });
            rootFolder = await Folder.findOne({
                where: { id: rootFolder.id },
                include: [{
                    model: ImageVisibilityType,
                    through: { model: FolderImageVisibility },
                    attributes: ['id'],
                }]
            });
        }
        const children = await this.getSubFolders(rootFolder.id, userId);
        rootFolder = rootFolder.get();
        rootFolder.visibilityTypeIds = rootFolder.image_visibility_types.map(v => v.id);
        delete rootFolder.image_visibility_types;
        rootFolder.children = children;
        return rootFolder;
    }

    async getSubFolders(parentId, userId) {
        const folders = await Folder.findAll({
            where: { parentId, userId },
            include: [{
                model: ImageVisibilityType,
                through: { model: FolderImageVisibility },
                attributes: ['id'],
            }],
            order: [
                ['name', 'asc']
            ]
        });
        for (const folder of folders) {
            const children = await this.getSubFolders(folder.id, userId);
            const visibilityTypeIds = folder.image_visibility_types.map(v => v.id);
            folder.setDataValue('visibilityTypeIds', visibilityTypeIds);
            folder.setDataValue('children', children);
            folder.setDataValue('image_visibility_types', undefined);
        }
        return folders.map(folder => folder.get());
    }

    async getFolderImageList(hashedId, folderId) {
        const userId = await this.checkUserAccess(hashedId);
        const folder = await Folder.findOne({
            where: {
                id: folderId,
                userId
            }
        });
        if (!folder) throw new Error('Folder not found');
        return await Image.findAll({
            where: {
                folderId: folder.id
            },
            order: [
                ['title', 'asc']
            ]
        });
    }

    async uploadImage(hashedId, file, formData) {
        const userId = await this.getUserId(hashedId);
        const processedImageName = await this.processAndUploadUserImage(file);
        const newImage = await this.createImageRecord(formData, userId, file, processedImageName);
        await this.saveImageVisibilities(newImage.id, formData.visibility);
        return newImage;
    }

    async processAndUploadUserImage(file) {
        try {
            const img = sharp(file.buffer);
            const metadata = await img.metadata();
            if (!metadata || !['jpeg', 'png', 'webp', 'gif'].includes(metadata.format)) {
                throw new Error('File is not a valid image');
            }
            if (metadata.width < 75 || metadata.height < 75) {
                throw new Error('Image dimensions are too small. Minimum size is 75x75 pixels.');
            }
            const resizedImg = img.resize({
                width: Math.min(metadata.width, 500),
                height: Math.min(metadata.height, 500),
                fit: sharp.fit.inside,
                withoutEnlargement: true
            });
            const newFileName = this.generateUniqueFileName(file.originalname);
            const filePath = this.buildFilePath(newFileName, 'user');
            await resizedImg.toFile(filePath);
            return newFileName;
        } catch (error) {
            throw new Error(`Failed to process image: ${error.message}`);
        }
    }


    async getUserId(hashedId) {
        return await this.checkUserAccess(hashedId);
    }

    generateUniqueFileName(originalFileName) {
        const uniqueHash = uuidv4();
        return uniqueHash;
    }

    buildFilePath(fileName, type) {
        const basePath = path.join(__dirname, '..', 'images', type);
        return path.join(basePath, fileName);
    }

    async saveFile(buffer, filePath) {
        try {
            await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
            await fsPromises.writeFile(filePath, buffer);
        } catch (error) {
            throw new Error(`Failed to save file: ${error.message}`);
        }
    }

    async createImageRecord(formData, userId, file, fileName) {
        try {
            return await Image.create({
                title: formData.title,
                description: formData.description || null,
                originalFileName: file.originalname,
                hash: fileName,
                folderId: formData.folderId,
                userId: userId,
            });
        } catch (error) {
            throw new Error(`Failed to create image record: ${error.message}`);
        }
    }

    async saveImageVisibilities(imageId, visibilities) {
        if (typeof visibilities === 'string') {
            visibilities = JSON.parse(visibilities);
        }
        if (!visibilities || !Array.isArray(visibilities)) {
            throw new Error('Invalid visibilities provided');
        }

        try {
            const visibilityPromises = visibilities.map(visibilityId => {
                return ImageImageVisibility.create({
                    imageId: imageId,
                    visibilityTypeId: visibilityId
                });
            });

            await Promise.all(visibilityPromises);
        } catch (error) {
            throw new Error(`Failed to save image visibilities: ${error.message}`);
        }
    }

    async getImage(imageId) {
        const image = await Image.findByPk(imageId);
        if (!image) throw new Error('Image not found');
        await this.checkUserAccess(image.userId);
        return image;
    }

    async loadUserByHash(hashedId) {
        console.log('Loading user by hashedId:', hashedId);
        return await User.findOne({ where: { hashedId: hashedId } });
    }

    async loadUserByName(userName) {
        return await User.findOne({ username: userName});
    }

    validateFolderData(data) {
        if (!data.name || typeof data.name !== 'string') throw new Error('Invalid folder data: Name is required');
    }

    validateImageData(imageData) {
        if (!imageData.url || typeof imageData.url !== 'string') throw new Error('Invalid image data: URL is required');
    }

    buildSearchWhereClause(username) {
        const whereClause = { active: true, searchable: true };
        if (username) {
            whereClause.username = { [Op.iLike]: `%${username}%` };
        }
        return whereClause;
    }

    getUserParamsInclude() {
        return [
            {
                model: UserParam,
                as: 'user_params',
                include: [{ model: UserParamType, as: 'paramType', required: true }]
            }
        ];
    }

    async filterUsersByCriteria(users, ageFrom, ageTo, genders) {
        const results = [];
        for (const user of users) {
            const userDetails = await this.extractUserDetails(user);
            if (this.isUserValid(userDetails, ageFrom, ageTo, genders)) {
                results.push(userDetails);
            }
        }
        return results;
    }

    async extractUserDetails(user) {
        const birthdateParam = user.user_params.find(param => param.paramType.description === 'birthdate');
        const genderParam = user.user_params.find(param => param.paramType.description === 'gender');
        const age = birthdateParam ? this.calculateAge(birthdateParam.value) : null;
        const gender = genderParam ? await this.getGenderValue(genderParam.value) : null;
        return {
            id: user.hashedId,
            username: user.username,
            email: user.email,
            gender,
            age
        };
    }

    async getGenderValue(genderId) {
        const genderValue = await UserParamValue.findOne({ where: { id: genderId } });
        return genderValue ? genderValue.value : null;
    }

    isUserValid(userDetails, ageFrom, ageTo, genders) {
        const { age, gender } = userDetails;
        const isWithinAgeRange = (!ageFrom || age >= ageFrom) && (!ageTo || age <= ageTo);
        const isGenderValid = !genders || !genders.length || (gender && genders.includes(gender));
        return isWithinAgeRange && isGenderValid && age >= 14;
    }

    async fetchUserProfile(hashedUserId) {
        return await User.findOne({
            where: { hashedId: hashedUserId, active: true, searchable: true },
            include: [
                {
                    model: UserParam,
                    as: 'user_params',
                    include: [
                        { model: UserParamType, as: 'paramType' },
                        { model: UserParamVisibility, as: 'param_visibilities', include: [{ model: UserParamVisibilityType, as: 'visibility_type' }] }
                    ],
                    order: [['order_id', 'asc']],
                },
                {
                    model: Friendship,
                    as: 'friendSender',
                },
                {
                    model: Friendship,
                    as: 'friendReceiver',
                }
            ]
        });
    }

    async constructUserProfile(user, hashedUserId) {
        const userParams = {};
        const requestingUser = await this.loadUserByHash(hashedUserId);
        if (!requestingUser) {
            throw new Error('User not found');
        }
        const requestingUserAge = await this.getUserAge(requestingUser.id);
        for (const param of user.user_params) {
            const visibility = param.param_visibilities?.[0]?.visibility_type?.description || 'Invisible';
            if (visibility === 'Invisible') continue;
            if (this.isVisibleToUser(visibility, requestingUserAge)) {
                userParams[param.paramType.description] = {
                    type: param.paramType.datatype,
                    value: await this.getParamValue(param)
                };
            }
        }
        let friendship = null;
        if (user.friendSender && user.friendSender.length > 0) {
            friendship = {
                isSender: true,
                accepted: user.friendSender[0].dataValues.accepted,
                denied: user.friendSender[0].dataValues.denied,
                withdrawn: user.friendSender[0].dataValues.withdrawn,
            }
        } else if (user.friendReceiver && user.friendReceiver.length > 0) {
            friendship = {
                isSender: false,
                accepted: user.friendReceiver[0].dataValues.accepted,
                denied: user.friendReceiver[0].dataValues.denied,
                withdrawn: user.friendReceiver[0].dataValues.withdrawn,
            }
        }
        return {
            username: user.username,
            registrationDate: user.registrationDate,
            friendship: friendship,
            params: userParams
        };
    }

    async getUserAge(userId) {
        const params = await this.getUserParams(userId, ['birthdate']);
        const birthdateParam = params.find(param => param.paramType.description === 'birthdate');
        return birthdateParam ? this.calculateAge(birthdateParam.value) : 0;
    }

    isVisibleToUser(visibility, requestingUserAge) {
        return visibility === 'All' ||
            (visibility === 'FriendsAndAdults' && requestingUserAge >= 18) ||
            (visibility === 'AdultsOnly' && requestingUserAge >= 18);
    }

    async getParamValue(param) {
        let paramValue = param.value;
        try {
            const parsedValue = JSON.parse(paramValue);
            if (Array.isArray(parsedValue)) {
                paramValue = await Promise.all(parsedValue.map(value => this.getValueFromDatabase(value, param.paramTypeId)));
            } else if (/^\d+$/.test(paramValue)) {
                paramValue = await this.getValueFromDatabase(paramValue, param.paramTypeId);
            }
        } catch (e) {
        }
        return paramValue;
    }

    async getValueFromDatabase(value, paramTypeId) {
        const userParamValue = await UserParamValue.findOne({
            where: { id: parseInt(value, 10), userParamTypeId: paramTypeId }
        });
        return userParamValue ? userParamValue.value : value;
    }

    async getPossibleImageVisibilities() {
        return await ImageVisibilityType.findAll();
    }

    async getImageFilePath(hashedUserId, hash) {
        const image = await Image.findOne({ where: { hash } });
        if (!image) {
            throw new Error('Image not found');
        }
        const userId = await this.checkUserAccess(hashedUserId);
        const hasAccess = await this.checkUserImageAccess(userId, image.id);
        if (!hasAccess) {
            throw new Error('Access denied');
        }
        const imagePath = this.buildFilePath(image.hash, 'user');
        if (!fs.existsSync(imagePath)) {
            throw new Error(`File "${imagePath}" not found`);
        }
        return imagePath;
    }

        // Public variant used by blog: allow access if the image's folder is visible to 'everyone'.
        async getImageFilePathPublicByHash(hash) {
            const image = await Image.findOne({ where: { hash } });
            if (!image) {
                throw new Error('Image not found');
            }
            const folder = await Folder.findOne({ where: { id: image.folderId } });
            if (!folder) {
                throw new Error('Folder not found');
            }
            const everyone = await ImageVisibilityType.findOne({ where: { description: 'everyone' } });
            if (!everyone) {
                throw new Error('Visibility type not found');
            }
            const hasEveryone = await FolderImageVisibility.findOne({ where: { folderId: folder.id, visibilityTypeId: everyone.id } });
            if (!hasEveryone) {
                const err = new Error('Access denied');
                err.status = 403;
                throw err;
            }
            const imagePath = this.buildFilePath(image.hash, 'user');
            if (!fs.existsSync(imagePath)) {
                throw new Error(`File "${imagePath}" not found`);
            }
            return imagePath;
        }

    async checkUserImageAccess(userId, imageId) {
        const image = await Image.findByPk(imageId);
        if (image.userId === userId) {
            return true;
        }
        const accessRules = await ImageImageVisibility.findAll({
            where: { imageId }
        });
        return accessRules.some(rule => {
            return false;
        });
    }

    async changeImage(hashedUserId, imageId, title, visibilities) {
        const userId = await this.checkUserAccess(hashedUserId);
        await this.checkUserImageAccess(userId, imageId);
        const image = await Image.findOne({ where: { id: imageId } });
        if (!image) {
            throw new Error('image not found')
        }
        await image.update({ title: title });
        await ImageImageVisibility.destroy({ where: { imageId } });
        for (const visibility of visibilities) {
            await ImageImageVisibility.create({ imageId, visibilityTypeId: visibility.id });
        }
        return image.folderId;
    }

    async getFoldersByUsername(username, hashedUserId) {
        const user = await this.loadUserByName(username);
        if (!user) {
            throw new Error('User not found');
        }
        const requestingUserId = await this.checkUserAccess(hashedUserId);
        let rootFolder = await Folder.findOne({ where: { parentId: null, userId: user.id } });
        if (!rootFolder) {
            return null;
        }
        const accessibleFolders = await this.getAccessibleFolders(rootFolder.id, requestingUserId);
        rootFolder = rootFolder.get();
        rootFolder.children = accessibleFolders;
        return rootFolder;
    }

    async getAccessibleFolders(folderId, requestingUserId) {
        const folderIdString = String(folderId);
        const requestingUserIdString = String(requestingUserId);
        const requestingUser = await User.findOne({ where: { id: requestingUserIdString } });
        const isAdult = this.isUserAdult(requestingUser.id);
        const accessibleFolders = await Folder.findAll({
            where: { parentId: folderIdString },
            include: [
                {
                    model: ImageVisibilityType,
                    through: { model: FolderImageVisibility },
                    where: {
                        [Op.or]: [
                            { description: 'everyone' },
                            { description: 'adults', ...(isAdult ? {} : { [Op.not]: null }) },
                            { description: 'friends-and-adults', ...(isAdult ? {} : { [Op.not]: null }) }
                        ]
                    },
                    required: false
                },
                {
                    model: ImageVisibilityUser,
                    through: { model: FolderVisibilityUser },
                    where: { user_id: requestingUserIdString },
                    required: false
                }
            ]
        });
        const folderList = [];
        for (let folder of accessibleFolders) {
            const children = await this.getAccessibleFolders(folder.id, requestingUserIdString);
            folder = folder.get();
            folder.children = children;
            folderList.push(folder);
        }
        return folderList;
    }

    async deleteFolder(hashedUserId, folderId) {
        const userId = await this.checkUserAccess(hashedUserId);
        const folder = await Folder.findOne({
            where: { id: folderId, userId }
        });
        if (!folder) {
            throw new Error('Folder not found or access denied');
        }
        await FolderImageVisibility.destroy({ where: { folderId: folder.id } });
        await folder.destroy();
        return true;
    }

    async createGuestbookEntry(hashedSenderId, recipientName, htmlContent, image) {
        const sender = await this.loadUserByHash(hashedSenderId);
        if (!sender) {
            throw new Error('Sender not found');
        }
        const recipient = await this.loadUserByName(recipientName);
        if (!recipient) {
            throw new Error('Recipient not found');
        }
        const sanitizedContent = this.sanitizeHtml(htmlContent);
        let uploadedImage = null;
        if (image) {
            uploadedImage = await this.processAndUploadGuestbookImage(image);
        }
        const entry = await GuestbookEntry.create({
            senderId: sender.id,
            recipientId: recipient.id,
            senderUsername: sender.username,
            contentHtml: sanitizedContent,
            imageUrl: uploadedImage
        });

        return entry;
    }

    sanitizeHtml(htmlContent) {
        const window = new JSDOM('').window;
        const purify = DOMPurify(window);
        const cleanHtml = purify.sanitize(htmlContent, {
            ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'ol', 'li', 'br'],
            ALLOWED_ATTR: ['href', 'title'],
            FORBID_TAGS: ['script', 'style', 'iframe', 'img'],
            ALLOWED_URI_REGEXP: /^https?:\/\//,
        });

        return cleanHtml;
    }

    async processAndUploadGuestbookImage(image) {
        try {
            const img = sharp(image.buffer);
            const metadata = await img.metadata();
            if (!metadata || !['jpeg', 'png', 'webp', 'gif'].includes(metadata.format)) {
                throw new Error('File is not a valid image');
            }
            if (metadata.width < 20 || metadata.height < 20) {
                throw new Error('Image dimensions are too small. Minimum size is 20x20 pixels.');
            }
            const resizedImg = img.resize({
                width: Math.min(metadata.width, 500),
                height: Math.min(metadata.height, 500),
                fit: sharp.fit.inside,
                withoutEnlargement: true
            });
            const newFileName = this.generateUniqueFileName(image.originalname);
            const filePath = this.buildFilePath(newFileName, 'guestbook');
            await resizedImg.toFile(filePath);
            return newFileName;
        } catch (error) {
            throw new Error(`Failed to process image: ${error.message}`);
        }
    }

    async getGuestbookEntries(hashedUserId, username, page = 1) {
        const pageSize = 20;
        const offset = (page - 1) * pageSize;
        this.checkUserAccess(hashedUserId);
        const user = await this.loadUserByName(username);
        if (!user) {
            throw new Error('User not found');
        }
        const entries = await GuestbookEntry.findAndCountAll({
            where: { recipientId: user.id },
            include: [
                { model: User, as: 'sender', attributes: ['username'] },
            ],
            limit: pageSize,
            offset: offset,
            order: [['createdAt', 'DESC']]
        });
        const resultList = entries.rows.map(entry => ({
            id: entry.id,
            sender: entry.sender ? entry.sender.username : entry.senderUsername,
            contentHtml: entry.contentHtml,
            withImage: entry.imageUrl !== null,
            createdAt: entry.createdAt
        }));
        return {
            entries: resultList,
            currentPage: page,
            totalPages: Math.ceil(entries.count / pageSize),
        };
    }

    async getGuestbookImageFilePath(hashedUserId, guestbookOwnerName, entryId) {
        await this.checkUserAccess(hashedUserId);

        const guestbookOwner = await this.loadUserByName(guestbookOwnerName);
        if (!guestbookOwner) {
            throw new Error('usernotfound');
        }
        const entry = await GuestbookEntry.findOne({
            where: { id: entryId, recipientId: guestbookOwner.id },
        });
        if (!entry) {
            throw new Error('entrynotfound');
        }
        if (!entry.imageUrl) {
            console.log(entry);
            throw new Error('entryhasnoimage');
        }
        console.log(`Image URL: ${entry.imageUrl}`);
        const imagePath = this.buildFilePath(entry.imageUrl, 'guestbook');
        if (!fs.existsSync(imagePath)) {
            throw new Error(imagenotfound);
        }
        return imagePath;
    }

    async deleteGuestbookEntry(hashedUserId, entryId) {
        const user = await this.loadUserByHash(hashedUserId);
        if (!user) {
            throw new Error('User not found');
        }
        const entry = await GuestbookEntry.findOne({ where: { id: entryId } });
        if (!entry) {
            throw new Error('Entry not found');
        }
        if (entry.senderId !== user.id && entry.recipientId !== user.id) {
            throw new Error('Not authorized to delete this entry');
        }
        await entry.destroy();
    }

    async createDiaryEntry(hashedUserId, text) {
        const userId = await this.checkUserAccess(hashedUserId);
        const newEntry = await Diary.create({
            userId: userId,
            text: text,
            createdAt: new Date(),
            updatedAt: new Date(),
        });
        return newEntry;
    }

    async updateDiaryEntry(diaryEntryId, hashedUserId, newText) {
        const userId = await this.checkUserAccess(hashedUserId);
        const existingEntry = await Diary.findOne({
            where: { id: diaryEntryId, userId: userId }
        });
        if (!existingEntry) {
            throw new Error('Diary entry not found or unauthorized access');
        }
        existingEntry.text = newText;
        existingEntry.updatedAt = new Date();
        await existingEntry.save();
        return existingEntry;
    }

    async deleteDiaryEntry(diaryEntryId, hashedUserId) {
        const userId = await this.checkUserAccess(hashedUserId);
        const entryToDelete = await Diary.findOne({
            where: { id: diaryEntryId, userId: userId }
        });
        if (!entryToDelete) {
            throw new Error('Diary entry not found or unauthorized access');
        }
        await entryToDelete.destroy();
        return true;
    }

    async getDiaryEntries(hashedUserId, page) {
        const userId = await this.checkUserAccess(hashedUserId);
        const entries = await Diary.findAndCountAll({
            where: { userId: userId },
            order: [['createdAt', 'DESC']],
            offset: (page - 1) * 20,
            limit: 20,
        });
        return { entries: entries.rows, totalPages: Math.ceil(entries.count / 20) };
    }

    async addFriend(hashedUserid, friendUserid) {
        console.log('--------', friendUserid, hashedUserid);
        const requestingUserId = await this.checkUserAccess(hashedUserid);
        const friend = await User.findOne({ where: { hashedId: friendUserid } });
        if (!friend) {
            throw new Error('notfound');
        }
        const friendship = await Friendship.findOne({
            where: {
                [Op.or]: [
                    { user1Id: requestingUserId, user2Id: friend.id },
                    { user1Id: friend.id, user2Id: requestingUserId }
                ]
            }
        });
        console.log('friendship', friend, requestingUserId);
        if (friendship) {
            if (friendship.withdrawn && friendship.user1Id === requestingUserId) {
                friendship.update({ withdrawn: false });
            } else {
                throw new Error('alreadyexists');
            }
        } else {
            await Friendship.create({ user1Id: requestingUserId, user2Id: friend.id });
        }
        return { accepted: false, withdrawn: false, denied: false };
    }

    async removeFriend(hashedUserid, friendUserid) {
        const requestingUserId = await this.checkUserAccess(hashedUserid);
        const friend = await this.loadUserByHash(friendUserid);
        if (!friend) {
            throw new Error('notfound');
        }
        const friendship = await Friendship.findOne({
            where: {
                [Op.or]: [
                    { user1Id: requestingUserId, user2Id: friend.id },
                    { user1Id: friend.id, user2Id: requestingUserId }
                ]
            }
        });
        if (!friendship) {
            throw new Error('notfound');
        }
        if (friendship.user1Id === requestingUserId) {
            friendship.update({ withdrawn: true })
        } else {
            friendship.update({ denied: true });
        }
        return true;
    }

    async acceptFriendship(hashedUserid, friendUserid) {
        const requestingUserId = await this.checkUserAccess(hashedUserid);
        const friend = await this.loadUserByHash(friendUserid);
        if (!friend) {
            throw new Error('notfound');
        }
        const friendship = await Friendship.findOne({
            where: {
                [Op.or]: [
                    { user1Id: requestingUserId, user2Id: friend.id },
                    { user1Id: friend.id, user2Id: requestingUserId }
                ]
            }
        });
        if (!friendship) {
            throw new Error('notfound');
        }
        if (friendship.user1Id === requestingUserId && friendship.withdrawn) {
            friendship.update({ withdrawn: false });
        } else if (friendship.user2Id === requestingUserId && friendship.denied) {
            friendship.update({ denied: false, accepted: true });
        } else {
            throw new Error('notfound');
        }
    }

    async getLoggedInFriends(hashedUserId) {
        const userId = await this.checkUserAccess(hashedUserId);
        const activeFriendships = await Friendship.findAll({
            where: {
                accepted: true,
                denied: false,
                withdrawn: false,
                [Op.or]: [
                    { user1Id: userId },
                    { user2Id: userId }
                ]
            }
        });
        const friendIds = activeFriendships.map(friendship =>
            friendship.user1Id === userId ? friendship.user2Id : friendship.user1Id
        );
        const loggedInFriends = [];
        for (const friendId of friendIds) {
            const session = await getUserSession(friendId);
            if (session && session.id) {
                const friend = await User.findOne({ where: { hashedId: session.id } });
                if (friend) {
                    loggedInFriends.push({
                        id: friend.hashedId,
                        username: friend.username,
                    }); 
                }
            }
        }
        return loggedInFriends;
    }
}
export default SocialNetworkService;
