import jwt from "jsonwebtoken"
import bcrypt from "bcrypt"
import { prisma } from "../../core/prisma"
import { AppError } from "../../core/AppError"
import { generateRefreshToken, hashToken } from "../../utils/auth.utils"
import { Prisma } from "@prisma/client"
import { buildShopUserPermissionSnapshot } from "../shop-user/shop-user-permissions.service"

const JWT_SECRET = process.env.JWT_SECRET!

type ShopUserForAuth = Prisma.ShopUserGetPayload<{
    include: {
        stations: {
            include: {
                station: true
            }
        }
    }
}>

export const posAuthService = {

    /**
     * Login par email / mot de passe.
     *
     * 1. Cherche tous les ShopUser actifs avec cet email.
     * 2. Vérifie le password sur le premier trouvé (password synchronisé entre shops).
     * 3. Si un seul shop → retourne directement les tokens (status: "authenticated").
     * 4. Si plusieurs shops → retourne un sessionToken temporaire + liste des shops (status: "select_shop").
     */
    async login(email: string, password: string) {
        const shopUsers = await prisma.shopUser.findMany({
            where: { email, active: true },
            include: {
                shop: { select: { id: true, name: true, createdAt: true } },
                stations: {
                    include: { station: true }
                }
            }
        })

        if (shopUsers.length === 0) {
            throw new AppError("Invalid credentials", 401)
        }

        // Vérifier le password (on prend le hash du premier, ils sont synchronisés)
        const valid = await bcrypt.compare(password, shopUsers[0].password)
        if (!valid) {
            throw new AppError("Invalid credentials", 401)
        }

        // Un seul shop → auth directe
        if (shopUsers.length === 1) {
            const tokens = await this.issueTokens(shopUsers[0])
            return {
                status: "authenticated" as const,
                ...tokens
            }
        }

        // Plusieurs shops → sessionToken temporaire (JWT court, 5 min)
        const sessionToken = jwt.sign(
            { email, type: "pos_session" },
            JWT_SECRET,
            { expiresIn: "5m" }
        )

        return {
            status: "select_shop" as const,
            sessionToken,
            shops: shopUsers.map(su => ({
                id: su.shop.id,
                name: su.shop.name,
                createdAt: su.shop.createdAt.toISOString()
            }))
        }
    },

    /**
     * Sélection du shop après un login multi-shop.
     * Valide le sessionToken temporaire puis émet les tokens finaux.
     */
    async selectShop(sessionToken: string, shopId: string) {
        let payload: { email: string; type: string }
        try {
            payload = jwt.verify(sessionToken, JWT_SECRET) as { email: string; type: string }
        } catch {
            throw new AppError("Session expired, please login again", 401)
        }

        if (payload.type !== "pos_session") {
            throw new AppError("Invalid session token", 401)
        }

        const shopUser = await prisma.shopUser.findFirst({
            where: {
                email: payload.email,
                shopId,
                active: true
            },
            include: {
                stations: {
                    include: { station: true }
                }
            }
        })

        if (!shopUser) {
            throw new AppError("Not authorized for this shop", 403)
        }

        return this.issueTokens(shopUser)
    },

    async issueTokens(shopUser: ShopUserForAuth) {
        const accessToken = jwt.sign(
            {
                sub: shopUser.id,
                type: "pos",
                mustChangePassword: shopUser.mustChangePassword
            },
            JWT_SECRET,
            { expiresIn: "1h" }
        )

        const refreshToken = generateRefreshToken()
        const tokenHash = hashToken(refreshToken)

        await prisma.shopUserRefreshToken.create({
            data: {
                shopUserId: shopUser.id,
                tokenHash,
                expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365)
            }
        })

        return {
            accessToken,
            refreshToken,
            shopUser: this.formatShopUser(shopUser)
        }
    },

    async refresh(refreshToken: string) {
        const tokenHash = hashToken(refreshToken)

        const stored = await prisma.shopUserRefreshToken.findUnique({
            where: { tokenHash },
            include: {
                shopUser: {
                    include: {
                        stations: {
                            include: {
                                station: true
                            }
                        }
                    }
                }
            }
        })

        if (!stored || stored.revoked)
            throw new AppError("Invalid refresh token", 401)

        if (stored.expiresAt < new Date())
            throw new AppError("Refresh expired", 401)

        await prisma.shopUserRefreshToken.update({
            where: { id: stored.id },
            data: { revoked: true }
        })

        return this.issueTokens(stored.shopUser as ShopUserForAuth)
    },

    async changePassword(shopUserId: string, newPassword: string) {
        const shopUser = await prisma.shopUser.findUnique({
            where: { id: shopUserId }
        })

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

        const hashed = await bcrypt.hash(newPassword, 10)

        // Mettre à jour le password pour toutes les entrées du même email (sync cross-shop)
        await prisma.shopUser.updateMany({
            where: { email: shopUser.email },
            data: {
                password: hashed,
                mustChangePassword: false
            }
        })

        // Révoquer tous les refresh tokens de toutes les entrées du même email
        const allShopUserIds = await prisma.shopUser.findMany({
            where: { email: shopUser.email },
            select: { id: true }
        })

        await prisma.shopUserRefreshToken.updateMany({
            where: { shopUserId: { in: allShopUserIds.map(u => u.id) } },
            data: { revoked: true }
        })

        const updated = await prisma.shopUser.findUnique({
            where: { id: shopUserId },
            include: {
                stations: {
                    include: {
                        station: true
                    }
                }
            }
        })

        return this.issueTokens(updated!)
    },

    /**
     * Switch de shop sans re-login.
     * Le JWT est shop-agnostic, on a juste besoin de retrouver le ShopUser
     * correspondant au même email dans le nouveau shop.
     */
    async switchShop(currentShopUserId: string, targetShopId: string) {
        const current = await prisma.shopUser.findUnique({
            where: { id: currentShopUserId },
            select: { email: true }
        })

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

        const targetShopUser = await prisma.shopUser.findFirst({
            where: {
                email: current.email,
                shopId: targetShopId,
                active: true
            },
            include: {
                stations: {
                    include: { station: true }
                }
            }
        })

        if (!targetShopUser) {
            throw new AppError("Not authorized for this shop", 403)
        }

        return {
            shopUser: this.formatShopUser(targetShopUser)
        }
    },

    /**
     * Profil complet du ShopUser connecté pour le shop courant,
     * + liste des shops disponibles (pour le switch).
     */
    async getProfile(shopUserId: string) {
        const shopUser = await prisma.shopUser.findUnique({
            where: { id: shopUserId },
            include: {
                stations: {
                    include: { station: true }
                }
            }
        })

        if (!shopUser || !shopUser.active) {
            throw new AppError("User not found or inactive", 404)
        }

        // Tous les shops où cet email a un compte actif
        const allShopUsers = await prisma.shopUser.findMany({
            where: { email: shopUser.email, active: true },
            include: {
                shop: { select: { id: true, name: true, createdAt: true } }
            }
        })

        return {
            shopUser: this.formatShopUser(shopUser),
            availableShops: allShopUsers.map(su => ({
                id: su.shop.id,
                name: su.shop.name,
                createdAt: su.shop.createdAt.toISOString()
            }))
        }
    },

    /**
     * Formatte un ShopUser Prisma en DTO.
     */
    formatShopUser(shopUser: ShopUserForAuth) {
        const { permissionOverrides, effectivePermissions } = buildShopUserPermissionSnapshot(shopUser)

        return {
            id: shopUser.id,
            shopId: shopUser.shopId,
            email: shopUser.email,
            displayName: shopUser.displayName,
            username: shopUser.username,
            role: shopUser.role,
            active: shopUser.active,
            mustChangePassword: shopUser.mustChangePassword,
            permissionOverrides,
            effectivePermissions,
            stations: shopUser.stations.map(({ station }) => ({
                id: station.id,
                name: station.name,
                mainType: station.mainType,
                active: station.active,
                revision: station.revision,
                createdAt: station.createdAt.toISOString()
            })),
            createdAt: shopUser.createdAt.toISOString()
        }
    }

}