import { prisma } from "../../core/prisma"
import { AppError } from "../../core/AppError"
import { tracked } from "../../core/tracked"
import bcrypt from "bcrypt"
import type {
    CreateShopUserInput,
    ShopUserListItemDto,
    UpdateShopUserInput
} from "ttm-shared"
import { buildShopUserPermissionSnapshot, serializeShopUserPermissionOverrides } from "./shop-user-permissions.service"

function mapShopUserListItem(user: any): ShopUserListItemDto {
    const { permissionOverrides, effectivePermissions } = buildShopUserPermissionSnapshot(user)

    return {
        id: user.id,
        email: user.email,
        displayName: user.displayName,
        username: user.username,
        role: user.role,
        active: user.active,
        mustChangePassword: user.mustChangePassword,
        permissionOverrides,
        effectivePermissions,
        createdAt: user.createdAt.toISOString(),
    }
}

export const shopUserService = {

    async create(
        shopId: string,
        input: CreateShopUserInput,
        ownerId: string,
        senderSocketId?: string
    ): Promise<ShopUserListItemDto> {

        // 🔒 Vérifie que le shop appartient à l'admin
        const shop = await prisma.shop.findUnique({
            where: { id: shopId }
        })

        if (!shop || shop.ownerId !== ownerId) {
            throw new AppError("Forbidden", 403)
        }

        const existing = await prisma.shopUser.findUnique({
            where: {
                shopId_email: {
                    shopId,
                    email: input.email
                }
            }
        })

        if (existing) {
            throw new AppError("Email already exists in this shop", 400)
        }

        // Si un ShopUser avec cet email existe dans un autre shop, réutiliser le même password hash
        const existingElsewhere = await prisma.shopUser.findFirst({
            where: { email: input.email }
        })

        const hashed = existingElsewhere
            ? existingElsewhere.password
            : await bcrypt.hash(input.password, 10)

        // Auto-generate username from email (part before @)
        const username = input.email.split("@")[0] ?? input.email

        const t = tracked({ shopId, entity: "ShopUser", actorUserId: ownerId, senderSocketId })

        const user: any = await t.create("shopUser", {
            shopId,
            email: input.email,
            displayName: input.displayName,
            username,
            password: hashed,
            role: input.role,
            permissionOverrides: serializeShopUserPermissionOverrides(input.permissionOverrides),
            mustChangePassword: existingElsewhere ? false : true
        }, undefined,
            "shop_user.created",
            {
                email: input.email,
                username,
                displayName: input.displayName,
                role: input.role,
                mustChangePassword: !existingElsewhere,
                permissionOverrides: serializeShopUserPermissionOverrides(input.permissionOverrides),
            }
        )

        return mapShopUserListItem(user)
    },

    async findAllByShop(shopId: string, ownerId: string) {
        // Vérifie que le shop appartient à l'admin
        const shop = await prisma.shop.findUnique({
            where: { id: shopId }
        })

        if (!shop || shop.ownerId !== ownerId) {
            throw new AppError("Forbidden", 403)
        }

        const users = await prisma.shopUser.findMany({
            where: { shopId },
            orderBy: { createdAt: 'desc' }
        })

        return users.map(mapShopUserListItem)
    },

    /**
     * Récupère tous les ShopUsers de tous les shops appartenant à un admin.
     */
    async findAllByOwner(ownerId: string) {
        const users = await prisma.shopUser.findMany({
            where: {
                shop: { ownerId }
            },
            include: {
                shop: { select: { id: true, name: true } }
            },
            orderBy: { createdAt: 'desc' }
        })

        return users.map(user => ({
            ...mapShopUserListItem(user),
            shopId: user.shopId,
            shopName: user.shop.name,
        }))
    },

    async update(
        shopId: string,
        userId: string,
        input: UpdateShopUserInput,
        ownerId: string,
        senderSocketId?: string
    ) {
        // Vérifie que le shop appartient à l'admin
        const shop = await prisma.shop.findUnique({
            where: { id: shopId }
        })

        if (!shop || shop.ownerId !== ownerId) {
            throw new AppError("Forbidden", 403)
        }

        const user = await prisma.shopUser.findFirst({
            where: { id: userId, shopId }
        })

        if (!user) {
            throw new AppError("User not found", 404)
        }

        const data: Record<string, any> = {
            ...(input.displayName !== undefined && { displayName: input.displayName }),
            ...(input.role !== undefined && { role: input.role }),
            ...(input.active !== undefined && { active: input.active }),
            ...(input.permissionOverrides !== undefined && {
                permissionOverrides: serializeShopUserPermissionOverrides(input.permissionOverrides),
            }),
        }

        const t = tracked({ shopId, entity: "ShopUser", actorUserId: ownerId, senderSocketId })

        const updated: any = await t.update("shopUser", { id: userId }, data, undefined,
            "shop_user.updated",
            { changes: Object.keys(data), ...data }
        )

        return mapShopUserListItem(updated)
    },

    async delete(shopId: string, userId: string, ownerId: string, senderSocketId?: string) {
        // Vérifie que le shop appartient à l'admin
        const shop = await prisma.shop.findUnique({
            where: { id: shopId }
        })

        if (!shop || shop.ownerId !== ownerId) {
            throw new AppError("Forbidden", 403)
        }

        const user = await prisma.shopUser.findFirst({
            where: { id: userId, shopId }
        })

        if (!user) {
            throw new AppError("User not found", 404)
        }

        const t = tracked({ shopId, entity: "ShopUser", actorUserId: ownerId, senderSocketId })

        // Soft delete avec revision + événement
        await t.update("shopUser", { id: userId }, { active: false }, undefined,
            "shop_user.deleted",
            { userId, username: user.username }
        )

        return { success: true }
    }
}