<template>
    <div>
        <PageHeader>
            {{ t('admin.auditLog.title') }}
            <template #actions>
                <MalioButton
                    variant="tertiary"
                    :label="t('audit.filters.title')"
                    icon-name="mdi:tune"
                    icon-position="left"
                    icon-size="24"
                    @click="openFilters"
                />
            </template>
        </PageHeader>

        <!-- Tableau -->
        <MalioDataTable
            :columns="columns"
            :items="rows"
            :total-items="totalItems"
            :page="filters.page ?? 1"
            :per-page="filters.itemsPerPage ?? 10"
            :per-page-options="[10, 25, 50]"
            :empty-message="isFiltered ? t('audit.no_results') : t('audit.empty')"
            @update:page="onPageChange"
            @update:per-page="onPerPageChange"
            @row-click="onRowClick"
        >
            <template #cell-action="{ item }">
                <span
                    class="inline-flex items-center rounded-full px-2 py-0.5 font-medium"
                    :class="actionBadgeClass(item.action as string)"
                >
                    {{ t(`audit.action.${item.action}`) }}
                </span>
            </template>
            <template #cell-entityType="{ item }">
                <span
                    :title="item.entityType as string"
                >{{ formatEntityType(item.entityType as string) }}</span>
            </template>
            <template #cell-entityId="{ item }">
                <span>{{ item.entityId }}</span>
            </template>
            <template #cell-summary="{ item }">
                <span class="text-gray-600">{{ item.summary }}</span>
            </template>
        </MalioDataTable>

        <!-- Drawer de filtres : etat brouillon, applique uniquement au clic sur
             "Voir les resultats". `body-class="p-0"` pour que l'accordeon aille
             bord a bord (les items portent leur propre px-7). -->
        <MalioDrawer
            v-model="filterDrawerOpen"
            drawer-class="max-w-[450px]"
            body-class="p-0"
            footer-class="justify-between border-t border-black p-6"
        >
            <template #header>
                <h2 class="text-[24px] font-bold uppercase">{{ t('audit.filters.title') }}</h2>
            </template>

            <MalioAccordion>
                <!-- Dates : deux champs date+heure Du / Au (champs datetime a l'origine) -->
                <MalioAccordionItem :title="t('audit.filters.date_range')" value="dates">
                    <!-- pb-4 sur les labels Du/Au : simule le slot message
                         du MalioDateTime voisin pour qu'items-center recentre
                         le label sur le centre visible du champ. -->
                    <div class="grid grid-cols-[auto_1fr] items-center gap-x-3">
                        <span class="pb-4">{{ t('audit.filters.date_from') }}</span>
                        <!-- Borne le picker "Du" par la valeur "Au" pour interdire une plage
                             inversee a la saisie (le backend renverrait silencieusement 0 ligne). -->
                        <MalioDateTime
                            v-model="draftDateFrom"
                            :max="draftDateTo ?? undefined"
                        />
                        <span class="pb-4">{{ t('audit.filters.date_to') }}</span>
                        <MalioDateTime
                            v-model="draftDateTo"
                            :min="draftDateFrom ?? undefined"
                        />
                    </div>
                </MalioAccordionItem>

                <!-- Type d'entite : cases a cocher (multi-selection) -->
                <MalioAccordionItem :title="t('audit.filters.entity_type')" value="entity">
                    <div class="flex flex-col">
                        <MalioCheckbox
                            v-for="opt in entityTypeOptions"
                            :id="`filter-entity-${opt.value}`"
                            :key="opt.value"
                            :label="opt.label"
                            :model-value="draftEntityTypes.includes(opt.value)"
                            @update:model-value="(val: boolean) => toggleEntity(opt.value, val)"
                        />
                    </div>
                </MalioAccordionItem>

                <!-- Action : boutons radio (selection unique, '' = toutes) -->
                <MalioAccordionItem :title="t('audit.filters.action')" value="action">
                    <MalioRadioButton
                        v-for="opt in actionOptions"
                        :key="opt.value"
                        v-model="draftAction"
                        name="audit-action"
                        :value="opt.value"
                        :label="opt.label"
                        group-class="mt-0"
                    />
                </MalioAccordionItem>

                <!-- Utilisateur : recherche texte (ILIKE partiel cote backend) -->
                <MalioAccordionItem :title="t('audit.filters.user')" value="user">
                    <MalioInputText
                        v-model="draftPerformedBy"
                        icon-name="mdi:account-search"
                    />
                </MalioAccordionItem>
            </MalioAccordion>

            <template #footer>
                <MalioButton
                    variant="tertiary"
                    :label="t('audit.filters.reset')"
                    button-class="w-m-btn-action"
                    @click="resetFilters"
                />
                <MalioButton
                    variant="primary"
                    :label="t('audit.filters.apply')"
                    button-class="w-[170px]"
                    @click="applyFilters"
                />
            </template>
        </MalioDrawer>

        <!-- Drawer detail : diff courant + timeline complete de l'entite -->
        <MalioDrawer
            v-model="drawerOpen"
            drawer-class="max-w-2xl"
        >
            <template #header>
                <h2 class="text-[24px] font-bold">
                    {{ drawerTitle }}
                </h2>
            </template>
            <div v-if="selectedEntry">
                <AuditLogDetail :entry="selectedEntry" />
                <div class="mt-4 border-t border-gray-200 pt-3">
                    <h3
                        class="text-sm font-medium text-gray-700 mb-2"
                        :title="selectedEntry.entityType"
                    >
                        {{ formatEntityType(selectedEntry.entityType) }} #{{ selectedEntry.entityId }}
                    </h3>
                    <AuditTimeline
                        :entity-type="selectedEntry.entityType"
                        :entity-id="selectedEntry.entityId"
                    />
                </div>
            </div>
        </MalioDrawer>
    </div>
</template>

<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import type { AuditLogEntry, AuditLogFilters } from '~/shared/types'

const { t, te } = useI18n()
const { can } = usePermissions()
const { fetchLogsCached, fetchEntityTypes } = useAuditLog()
const toast = useToast()

// Traduit un identifiant `module.Entity` (ex: `core.User`, `sites.Site`) en
// libelle lisible via la cle i18n `audit.entity.<module>_<entity>`. Si aucune
// traduction n'existe, on retombe sur l'identifiant brut pour rester debug-friendly.
function formatEntityType(type: string): string {
    const key = `audit.entity.${type.toLowerCase().replace(/\./g, '_')}`
    return te(key) ? t(key) : type
}

// Protection cote UI : le middleware `modules.global.ts` filtre deja les
// routes desactivees, mais si quelqu'un atterit ici sans la permission on
// renvoie une 403 plutot que de flasher un ecran vide.
if (!can('core.audit_log.view')) {
    throw createError({ statusCode: 403, statusMessage: 'Forbidden' })
}

useHead({ title: t('admin.auditLog.title') })

// Etat des filtres APPLIQUES : pilote `loadEntries`. Local uniquement, JAMAIS
// persiste dans l'URL (cf. regle CLAUDE.md "Tableau : pas de persistance URL").
// `performedAtAfter`/`performedAtBefore` stockent une date+heure ISO naive
// (`YYYY-MM-DDTHH:MM:00`, fournie par MalioDateTime), convertie en ISO UTC
// au moment du fetch.
const filters = reactive<AuditLogFilters>({
    performedAtAfter:  undefined,
    performedAtBefore: undefined,
    entityType:        undefined,
    performedBy:       undefined,
    action:            undefined,
    page:              1,
    itemsPerPage:      10,
})

// Etat BROUILLON du drawer de filtres : edite librement, recopie dans `filters`
// uniquement au clic sur "Voir les resultats". Permet d'annuler une saisie en
// fermant le drawer sans relancer de requete.
const filterDrawerOpen = ref(false)
const draftDateFrom = ref<string | null>(null)
const draftDateTo = ref<string | null>(null)
const draftEntityTypes = ref<string[]>([])
const draftAction = ref<string>('')
const draftPerformedBy = ref<string>('')

// Liste des entity types (distincts) pour alimenter les cases a cocher.
const entityTypes = ref<string[]>([])
const entityTypeOptions = computed(() =>
    entityTypes.value.map(type => ({ value: type, label: formatEntityType(type) })),
)

// Actions : '' = "toutes". Sert d'options aux boutons radio.
const actionOptions = [
    { value: '',       label: t('audit.filters.all_actions') },
    { value: 'create', label: t('audit.action.create') },
    { value: 'update', label: t('audit.action.update') },
    { value: 'delete', label: t('audit.action.delete') },
]

const entries = ref<AuditLogEntry[]>([])
const totalItems = ref(0)
const loading = ref(false)

const drawerOpen = ref(false)
const selectedEntry = ref<AuditLogEntry | null>(null)

const columns = [
    { key: 'performedAt', label: t('admin.auditLog.table.performedAt') },
    { key: 'performedBy', label: t('admin.auditLog.table.performedBy') },
    { key: 'entityType',  label: t('admin.auditLog.table.entityType') },
    { key: 'entityId',    label: t('admin.auditLog.table.entityId') },
    { key: 'action',      label: t('admin.auditLog.table.action') },
    { key: 'summary',     label: t('admin.auditLog.table.summary') },
]

// Transforme chaque AuditLogEntry en ligne compatible MalioDataTable.
// On conserve `id` pour retrouver l'entry complete sur row-click.
const rows = computed(() =>
    entries.value.map(entry => ({
        id: entry.id,
        performedAt: formatDate(entry.performedAt),
        performedBy: entry.performedBy,
        entityType:  entry.entityType,
        entityId:    entry.entityId,
        action:      entry.action,
        summary:     summarize(entry),
    })),
)

const drawerTitle = computed(() =>
    selectedEntry.value
        ? `${formatEntityType(selectedEntry.value.entityType)} #${selectedEntry.value.entityId}`
        : t('audit.detail_title'),
)

const isFiltered = computed(() =>
    Boolean(filters.performedAtAfter || filters.performedAtBefore
        || (Array.isArray(filters.entityType) ? filters.entityType.length : filters.entityType)
        || filters.performedBy || filters.action),
)

// Anti-race : chaque fetch incremente un compteur ; seul le dernier en date
// ecrit les resultats dans `entries`/`totalItems`. Evite qu'une reponse tardive
// (reseau lent) n'ecrase les resultats d'une requete ulterieure.
let requestToken = 0

// Ouvre le drawer en recopiant l'etat applique vers le brouillon, pour que la
// reouverture reflete les filtres actifs.
function openFilters(): void {
    draftDateFrom.value = filters.performedAtAfter ?? null
    draftDateTo.value = filters.performedAtBefore ?? null
    draftEntityTypes.value = Array.isArray(filters.entityType)
        ? [...filters.entityType]
        : (filters.entityType ? [filters.entityType] : [])
    draftAction.value = filters.action ?? ''
    draftPerformedBy.value = filters.performedBy ?? ''
    filterDrawerOpen.value = true
}

// Bascule un type d'entite dans le brouillon (multi-selection). Les valeurs
// sont uniques par construction (v-for sur entityTypeOptions), pas besoin de Set.
function toggleEntity(value: string, selected: boolean): void {
    draftEntityTypes.value = selected
        ? [...draftEntityTypes.value, value]
        : draftEntityTypes.value.filter(v => v !== value)
}

// "Reinitialiser" : vide le brouillon ET les filtres actifs, puis recharge.
// La remise a zero s'applique immediatement (la table revient a la liste
// complete) ; le drawer reste ouvert pour montrer le formulaire vide.
function resetFilters(): void {
    draftDateFrom.value = null
    draftDateTo.value = null
    draftEntityTypes.value = []
    draftAction.value = ''
    draftPerformedBy.value = ''

    filters.performedAtAfter = undefined
    filters.performedAtBefore = undefined
    filters.entityType = undefined
    filters.action = undefined
    filters.performedBy = undefined
    filters.page = 1
    loadEntries()
}

// "Voir les resultats" : applique le brouillon, recharge et ferme le drawer.
function applyFilters(): void {
    filters.performedAtAfter = draftDateFrom.value ?? undefined
    filters.performedAtBefore = draftDateTo.value ?? undefined
    filters.entityType = draftEntityTypes.value.length > 0 ? [...draftEntityTypes.value] : undefined
    filters.action = draftAction.value === '' ? undefined : draftAction.value
    filters.performedBy = draftPerformedBy.value.trim() === '' ? undefined : draftPerformedBy.value.trim()
    filters.page = 1
    filterDrawerOpen.value = false
    loadEntries()
}

async function loadEntries(): Promise<void> {
    const token = ++requestToken
    loading.value = true
    try {
        const data = await fetchLogsCached({
            ...filters,
            // MalioDateTime fournit une date+heure sans fuseau (heure locale) ;
            // on la convertit en ISO UTC pour l'API (bornes exactes, intervalle inclusif).
            performedAtAfter:  filters.performedAtAfter ? toIso(filters.performedAtAfter) : undefined,
            performedAtBefore: filters.performedAtBefore ? toIso(filters.performedAtBefore) : undefined,
        })
        // Reponse obsolete (un fetch plus recent a ete lance entre-temps) :
        // on ignore le resultat pour ne pas overwrite l'etat courant.
        if (token !== requestToken) return
        entries.value = data.member ?? []
        totalItems.value = data.totalItems ?? 0
    } catch (err) {
        // useAuditLog appelle useApi avec { toast: false } pour ne pas multiplier
        // les toasts, donc c'est ici qu'on fait remonter l'erreur. Sans ce log+toast,
        // une RangeError de `toIso` (date invalide) ou une 500 API laissait l'utilisateur
        // devant une table vide indistinguable d'un filtre a zero resultat.
        if (token === requestToken) {
            entries.value = []
            totalItems.value = 0
            console.error('[audit-log] loadEntries failed', err)
            toast.error({
                title: t('audit.error.title'),
                message: t('audit.error.message'),
            })
        }
    } finally {
        if (token === requestToken) {
            loading.value = false
        }
    }
}

function toIso(localDateTime: string): string {
    // MalioDateTime emet une date+heure sans fuseau (heure murale locale) ;
    // on laisse Date() generer l'ISO UTC correspondant pour l'API.
    return new Date(localDateTime).toISOString()
}

function formatDate(iso: string): string {
    return new Date(iso).toLocaleString('fr-FR', {
        dateStyle: 'short',
        timeStyle: 'short',
    })
}

function actionBadgeClass(action: string): string {
    switch (action) {
        case 'create': return 'bg-green-100 text-green-800'
        case 'update': return 'bg-yellow-100 text-yellow-800'
        case 'delete': return 'bg-red-100 text-red-800'
        default: return 'bg-gray-100 text-gray-800'
    }
}

function summarize(entry: AuditLogEntry): string {
    const keys = Object.keys(entry.changes)
    if (keys.length === 0) return '—'
    if (keys.length <= 3) return keys.join(', ')
    return `${keys.slice(0, 3).join(', ')}… (+${keys.length - 3})`
}

function onRowClick(item: Record<string, unknown>): void {
    const entry = entries.value.find(e => e.id === item.id)
    if (entry) {
        selectedEntry.value = entry
        drawerOpen.value = true
    }
}

function onPageChange(value: number): void {
    filters.page = value
    loadEntries()
}

function onPerPageChange(value: number): void {
    filters.itemsPerPage = value
    filters.page = 1
    loadEntries()
}

onMounted(async () => {
    // Charge les entity types ET la liste principale en parallele (TTFD divise
    // par 2 sur un backend lent). Le `.catch` du premier garantit qu'un echec
    // de /audit-log-entity-types ne bloque pas l'affichage du tableau —
    // l'utilisateur perd juste le filtre, pas la page entiere.
    await Promise.all([
        fetchEntityTypes()
            .then(types => { entityTypes.value = types })
            .catch(() => { entityTypes.value = [] }),
        loadEntries(),
    ])
})
</script>
