<script setup lang="ts">
/**
 * Админка → Тенанты. Список всех тенантов SaaS с балансами/тарифами/MRR.
 *
 * Масштаб (28.06.2026): серверная пагинация + серверные фильтры (search/статус/тариф).
 * Раньше грузили всех разом и фильтровали в браузере — на 1000 клиентов это не
 * «смотрибельно» (поиск/чипы видели только первую страницу). Теперь:
 *   - страница из `limit/offset` (perPage), счётчик `total` с сервера → v-pagination;
 *   - поиск (org/subdomain/email ILIKE) — серверный, debounce 400мс;
 *   - статус (производный trial/overdue/active/suspended) и тариф — серверные multi.
 * Бэкенд: AdminTenantsController::index (statuses/tariffs/search/limit/offset/total).
 *
 * State (filterStatuses/filterTariffs/clearFilters/tenantsState/stats и др.) остаётся
 * в этом view ради `defineExpose`-контракта Vitest-тестов.
 *
 * Источник дизайна: liderra_v8_handoff/concepts/v8_admin.html секция #page-tenants.
 * Click по строке → /admin/tenants/{code} (карточка тенанта).
 */
import { onMounted, reactive, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { type AdminTenant, type TenantStatus } from '../../composables/mockTenants';
import { mapApiAdminTenant } from '../../composables/adminTenantsMapper';
import { usePolling } from '../../composables/usePolling';
import * as adminApi from '../../api/admin';
// NB: ImpersonationDialog оставлен sync-import — Vitest test через
// `findComponent({ name: 'ImpersonationDialog' })` + `stubs`, defineAsyncComponent
// ломает identity wrapper'а в test-utils.
import ImpersonationDialog from '../../components/admin/ImpersonationDialog.vue';
import TenantBalanceDialog from '../../components/admin/TenantBalanceDialog.vue';
import TenantsStatsHeader from '../../components/admin/tenants/TenantsStatsHeader.vue';
import TenantsFilters from '../../components/admin/tenants/TenantsFilters.vue';
import TenantsTable from '../../components/admin/tenants/TenantsTable.vue';

const router = useRouter();

const tenantsState = reactive<AdminTenant[]>([]);
const stats = reactive({ total: 0, active: 0, trial: 0, overdue: 0, monthlyRevenueRub: 0 });
const loading = ref(false);
const fetchError = ref(false);

const search = ref('');
const filterStatuses = ref<TenantStatus[]>([]);
const filterTariffs = ref<string[]>([]);
const availableTariffs = ref<string[]>([]);

// Серверная пагинация.
const page = ref(1);
const perPage = ref(25);
const total = ref(0);
const totalPages = () => Math.max(1, Math.ceil(total.value / perPage.value));

async function loadTenants(): Promise<void> {
    loading.value = true;
    fetchError.value = false;
    try {
        const res = await adminApi.listAdminTenants({
            search: search.value.trim(),
            statuses: filterStatuses.value.join(','),
            tariffs: filterTariffs.value.join(','),
            limit: perPage.value,
            offset: (page.value - 1) * perPage.value,
        });
        const mapped = res.tenants.map((t) => mapApiAdminTenant(t));
        tenantsState.splice(0, tenantsState.length, ...mapped);
        total.value = res.total;
        stats.total = res.stats.total;
        stats.active = res.stats.active;
        stats.trial = res.stats.trial;
        stats.overdue = res.stats.overdue;
    } catch {
        fetchError.value = true;
        tenantsState.splice(0, tenantsState.length);
    } finally {
        loading.value = false;
    }
}

// Опции тарифов для дропдауна — отдельным запросом (на странице видна только часть
// тенантов, поэтому список тарифов нельзя выводить из загруженного набора).
async function loadTariffOptions(): Promise<void> {
    try {
        const plans = await adminApi.listAdminTariffPlans();
        availableTariffs.value = Array.from(new Set(plans.map((p) => p.name))).sort();
    } catch {
        // дропдаун останется пустым — не критично для основного списка.
    }
}

// Поиск — debounce 400мс (планшет: печатает → ищет, без кнопки «Найти»).
let searchTimer: ReturnType<typeof setTimeout> | null = null;
watch(search, () => {
    if (searchTimer) clearTimeout(searchTimer);
    searchTimer = setTimeout(() => {
        page.value = 1;
        void loadTenants();
    }, 400);
});

// Фильтры — сразу перезагрузка с 1-й страницы.
watch(
    [filterStatuses, filterTariffs],
    () => {
        page.value = 1;
        void loadTenants();
    },
    { deep: true },
);

function goPage(p: number): void {
    page.value = p;
    void loadTenants();
}

onMounted(() => {
    void loadTariffOptions();
    void loadTenants();
});
usePolling(loadTenants);

function openTenantDetail(t: AdminTenant): void {
    router.push({ name: 'admin-tenant-detail', params: { code: t.code } });
}

function clearFilters(): void {
    filterStatuses.value = [];
    filterTariffs.value = [];
}

const impersonationOpen = ref(false);
const impersonationTenant = ref<AdminTenant | null>(null);

const balanceDialogOpen = ref(false);
const balanceTarget = ref<AdminTenant | null>(null);

const ADMIN_USER_ID = 1;

function openImpersonation(tenant: AdminTenant): void {
    impersonationTenant.value = tenant;
    impersonationOpen.value = true;
}

function openBalanceDialog(tenant: AdminTenant): void {
    balanceTarget.value = tenant;
    balanceDialogOpen.value = true;
}

async function onBalanceSaved(): Promise<void> {
    await loadTenants();
}

defineExpose({
    filterStatuses,
    filterTariffs,
    clearFilters,
    impersonationOpen,
    impersonationTenant,
    balanceDialogOpen,
    balanceTarget,
    tenantsState,
    stats,
    loading,
    fetchError,
    loadTenants,
    search,
    page,
    perPage,
    total,
    availableTariffs,
    goPage,
});
</script>

<template>
    <v-container fluid class="admin-tenants pa-6">
        <TenantsStatsHeader :stats="stats" :loading="loading" @refresh="loadTenants" />

        <v-alert
            v-if="fetchError"
            type="warning"
            variant="tonal"
            density="compact"
            closable
            class="mt-3"
            data-testid="fetch-error-alert"
        >
            Не удалось загрузить тенантов. Попробуйте обновить.
        </v-alert>

        <TenantsFilters
            :search="search"
            :filter-statuses="filterStatuses"
            :filter-tariffs="filterTariffs"
            :available-tariffs="availableTariffs"
            @update:search="search = $event"
            @update:filter-statuses="filterStatuses = $event"
            @update:filter-tariffs="filterTariffs = $event"
            @clear="clearFilters"
        />

        <TenantsTable
            :tenants="tenantsState"
            @row-click="openTenantDetail"
            @impersonate="openImpersonation"
            @edit-balance="openBalanceDialog"
        />

        <div class="d-flex align-center justify-space-between mt-3 flex-wrap ga-2">
            <span class="text-medium-emphasis text-body-2">Всего: {{ total }}</span>
            <v-pagination
                v-model="page"
                :length="totalPages()"
                :total-visible="7"
                density="compact"
                data-testid="tenants-pager"
                @update:model-value="goPage"
            />
        </div>

        <ImpersonationDialog v-model="impersonationOpen" :tenant="impersonationTenant" :requested-by="ADMIN_USER_ID" />

        <TenantBalanceDialog
            v-if="balanceTarget"
            v-model="balanceDialogOpen"
            :tenant-id="balanceTarget.id"
            :tenant-name="balanceTarget.name"
            :current-balance-rub="balanceTarget.balanceRub"
            @saved="onBalanceSaved"
        />
    </v-container>
</template>

<style scoped>
.admin-tenants {
    max-width: 1440px;
}
</style>
