import { PushAppState, PushPlatform, type PosPushInstallation } from "@prisma/client"
import { prisma } from "@/core/prisma"
import { AppError } from "@/core/AppError"
import { shopEventService } from "@/modules/shop/shop-event.service"
import type { PushAdminInstallationDto, PushAdminLogEntryDto } from "ttm-shared"
import type { PushHeartbeatInput, PushRegisterInput } from "./push.schema"

const INSTALLATION_SELECT = {
    id: true,
    shopId: true,
    shopUserId: true,
    terminalId: true,
    platform: true,
    appVersion: true,
    adminEnabled: true,
    isActive: true,
    appState: true,
    lastSeenAt: true,
    lastForegroundAt: true,
    lastPushSentAt: true,
    lastPushEvent: true,
    lastFirebaseErrorCode: true,
    lastFirebaseErrorAt: true,
    createdAt: true,
    updatedAt: true,
} as const

type InstallationRecord = Pick<PosPushInstallation,
    "id"
    | "shopId"
    | "shopUserId"
    | "terminalId"
    | "platform"
    | "appVersion"
    | "adminEnabled"
    | "isActive"
    | "appState"
    | "lastSeenAt"
    | "lastForegroundAt"
    | "lastPushSentAt"
    | "lastPushEvent"
    | "lastFirebaseErrorCode"
    | "lastFirebaseErrorAt"
    | "createdAt"
    | "updatedAt"
>

export type PushInstallationView = {
    id: string
    shopId: string
    shopUserId: string | null
    terminalId: string
    platform: PushPlatform
    appVersion: string | null
    adminEnabled: boolean
    isActive: boolean
    appState: PushAppState
    lastSeenAt: string
    lastForegroundAt: string | null
    lastPushSentAt: string | null
    lastPushEvent: string | null
    lastFirebaseErrorCode: string | null
    lastFirebaseErrorAt: string | null
    createdAt: string
    updatedAt: string
}

type AdminInstallationRecord = InstallationRecord & {
    shopUser?: {
        displayName: string | null
        username: string
        email: string
    } | null
}

function maskToken(token: string): string {
    return token.length <= 12 ? token : `${token.slice(0, 6)}…${token.slice(-6)}`
}

function resolveShopUserName(record: AdminInstallationRecord): string | null {
    if (!record.shopUser) return null
    return record.shopUser.displayName ?? record.shopUser.username ?? record.shopUser.email ?? null
}

function toInstallationView(record: InstallationRecord): PushInstallationView {
    return {
        id: record.id,
        shopId: record.shopId,
        shopUserId: record.shopUserId,
        terminalId: record.terminalId,
        platform: record.platform,
        appVersion: record.appVersion,
        adminEnabled: record.adminEnabled,
        isActive: record.isActive,
        appState: record.appState,
        lastSeenAt: record.lastSeenAt.toISOString(),
        lastForegroundAt: record.lastForegroundAt?.toISOString() ?? null,
        lastPushSentAt: record.lastPushSentAt?.toISOString() ?? null,
        lastPushEvent: record.lastPushEvent ?? null,
        lastFirebaseErrorCode: record.lastFirebaseErrorCode ?? null,
        lastFirebaseErrorAt: record.lastFirebaseErrorAt?.toISOString() ?? null,
        createdAt: record.createdAt.toISOString(),
        updatedAt: record.updatedAt.toISOString(),
    }
}

function toAdminInstallationDto(record: AdminInstallationRecord & { fcmToken: string }): PushAdminInstallationDto {
    return {
        ...toInstallationView(record),
        shopUserName: resolveShopUserName(record),
        fcmTokenMasked: maskToken(record.fcmToken),
    }
}

function buildForegroundPatch(appState: PushAppState | undefined, now: Date) {
    return appState === PushAppState.FOREGROUND
        ? { lastForegroundAt: now }
        : {}
}

function buildStaleThreshold(now = Date.now()) {
    const ttlMinutesRaw = Number(process.env.PUSH_INSTALLATION_TTL_MINUTES ?? 60 * 24 * 7)
    const ttlMinutes = Number.isFinite(ttlMinutesRaw) && ttlMinutesRaw > 0
        ? ttlMinutesRaw
        : 60 * 24 * 7

    return new Date(now - ttlMinutes * 60_000)
}

export const pushService = {
    async registerInstallation(input: PushRegisterInput & { shopId: string; shopUserId: string }) {
        const now = new Date()

        const installation = await prisma.$transaction(async (tx) => {
            const terminalMatch = await tx.posPushInstallation.findUnique({
                where: { terminalId: input.terminalId },
            })
            const tokenMatch = await tx.posPushInstallation.findUnique({
                where: { fcmToken: input.fcmToken },
            })

            const primary = terminalMatch ?? tokenMatch
            const duplicate = terminalMatch && tokenMatch && terminalMatch.id !== tokenMatch.id
                ? tokenMatch
                : null

            if (duplicate) {
                await tx.posPushInstallation.delete({ where: { id: duplicate.id } })
            }

            const payload = {
                shopId: input.shopId,
                shopUserId: input.shopUserId,
                terminalId: input.terminalId,
                fcmToken: input.fcmToken,
                platform: input.platform,
                appVersion: input.appVersion ?? null,
                isActive: true,
                appState: input.appState,
                lastSeenAt: now,
                lastFirebaseErrorCode: null,
                lastFirebaseErrorAt: null,
                ...buildForegroundPatch(input.appState as PushAppState, now),
            }

            if (primary) {
                return tx.posPushInstallation.update({
                    where: { id: primary.id },
                    data: payload,
                    select: INSTALLATION_SELECT,
                })
            }

            return tx.posPushInstallation.create({
                data: payload,
                select: INSTALLATION_SELECT,
            })
        })

        return {
            ok: true as const,
            installation: toInstallationView(installation),
        }
    },

    async heartbeatInstallation(input: PushHeartbeatInput & { shopId: string; shopUserId: string }) {
        const installation = await prisma.posPushInstallation.findUnique({
            where: { terminalId: input.terminalId },
        })

        if (!installation || installation.shopId !== input.shopId) {
            throw new AppError("Push installation not found", 404)
        }

        if (installation.shopUserId !== input.shopUserId) {
            throw new AppError("Push installation does not belong to current user", 403)
        }

        const now = new Date()
        const nextAppState = input.appState ?? installation.appState
        const updated = await prisma.posPushInstallation.update({
            where: { id: installation.id },
            data: {
                isActive: true,
                appState: nextAppState,
                lastSeenAt: now,
                ...buildForegroundPatch(nextAppState, now),
            },
            select: INSTALLATION_SELECT,
        })

        return {
            ok: true as const,
            lastSeenAt: updated.lastSeenAt.toISOString(),
            appState: updated.appState,
        }
    },

    async unregisterInstallation(input: { shopId: string; shopUserId: string; terminalId: string }) {
        const installation = await prisma.posPushInstallation.findUnique({
            where: { terminalId: input.terminalId },
        })

        if (!installation || installation.shopId !== input.shopId || installation.shopUserId !== input.shopUserId) {
            return {
                ok: true as const,
                terminalId: input.terminalId,
                isActive: false,
            }
        }

        const now = new Date()
        const updated = await prisma.posPushInstallation.update({
            where: { id: installation.id },
            data: {
                isActive: false,
                appState: PushAppState.UNKNOWN,
                shopUserId: null,
                lastSeenAt: now,
            },
        })

        return {
            ok: true as const,
            terminalId: updated.terminalId,
            isActive: updated.isActive,
        }
    },

    async getInstallationStatus(input: { shopId: string; shopUserId: string; terminalId?: string }) {
        const installation = input.terminalId
            ? await prisma.posPushInstallation.findFirst({
                where: {
                    terminalId: input.terminalId,
                    shopId: input.shopId,
                    OR: [
                        { shopUserId: input.shopUserId },
                        { shopUserId: null },
                    ],
                },
                select: INSTALLATION_SELECT,
            })
            : await prisma.posPushInstallation.findFirst({
                where: {
                    shopId: input.shopId,
                    shopUserId: input.shopUserId,
                },
                orderBy: { lastSeenAt: "desc" },
                select: INSTALLATION_SELECT,
            })

        return {
            ok: true as const,
            installation: installation ? toInstallationView(installation) : null,
        }
    },

    async listEligibleInstallations(shopId: string, shopUserId: string) {
        return prisma.posPushInstallation.findMany({
            where: {
                shopId,
                shopUserId,
                adminEnabled: true,
                isActive: true,
                lastSeenAt: { gte: buildStaleThreshold() },
            },
            orderBy: { lastSeenAt: "desc" },
        })
    },

    async listAdminInstallations(shopId: string): Promise<PushAdminInstallationDto[]> {
        const installations = await prisma.posPushInstallation.findMany({
            where: { shopId },
            orderBy: [
                { lastSeenAt: "desc" },
                { createdAt: "desc" },
            ],
            select: {
                ...INSTALLATION_SELECT,
                fcmToken: true,
                shopUser: {
                    select: {
                        displayName: true,
                        username: true,
                        email: true,
                    },
                },
            },
        })

        return installations.map((installation) => toAdminInstallationDto(installation))
    },

    async setAdminEnabled(input: { shopId: string; installationId: string; adminEnabled: boolean; actorUserId: string }) {
        const installation = await prisma.posPushInstallation.findFirst({
            where: {
                id: input.installationId,
                shopId: input.shopId,
            },
        })

        if (!installation) {
            throw new AppError("Push installation not found", 404)
        }

        const updated = await prisma.posPushInstallation.update({
            where: { id: installation.id },
            data: {
                adminEnabled: input.adminEnabled,
            },
            select: {
                ...INSTALLATION_SELECT,
                fcmToken: true,
                shopUser: {
                    select: {
                        displayName: true,
                        username: true,
                        email: true,
                    },
                },
            },
        })

        await shopEventService.createEvent(
            input.shopId,
            "push.installation.admin_state_changed",
            {
                installationId: installation.id,
                terminalId: installation.terminalId,
                adminEnabled: input.adminEnabled,
            },
            {
                entity: "PosPushInstallation",
                entityId: installation.id,
                actorUserId: input.actorUserId,
            },
        )

        return {
            ok: true as const,
            installation: toAdminInstallationDto(updated),
        }
    },

    async listPushLogs(shopId: string, limit = 50): Promise<PushAdminLogEntryDto[]> {
        const logs = await prisma.shopEvent.findMany({
            where: {
                shopId,
                type: { startsWith: "push." },
            },
            orderBy: { createdAt: "desc" },
            take: Math.min(Math.max(limit, 1), 200),
        })

        return logs.map((log) => ({
            id: log.id,
            type: log.type,
            entityId: log.entityId ?? null,
            actorUserId: log.actorUserId ?? null,
            createdAt: log.createdAt.toISOString(),
            payload: (log.payload ?? {}) as Record<string, unknown>,
        }))
    },

    async markPushDelivered(installationId: string, event: string) {
        await prisma.posPushInstallation.update({
            where: { id: installationId },
            data: {
                lastPushSentAt: new Date(),
                lastPushEvent: event,
                lastFirebaseErrorCode: null,
                lastFirebaseErrorAt: null,
            },
        })
    },

    async markPushFailure(installationId: string, code: string | null, deactivate = false) {
        await prisma.posPushInstallation.update({
            where: { id: installationId },
            data: {
                isActive: deactivate ? false : undefined,
                appState: deactivate ? PushAppState.UNKNOWN : undefined,
                lastFirebaseErrorCode: code,
                lastFirebaseErrorAt: new Date(),
            },
        })
    },
}

