import { createElement, type ReactNode } from 'react';
import { renderToString } from 'react-dom/server';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { RuleSectionValues } from '../types';
import YieldRuleEditor, { type YieldConfigData } from './YieldRuleEditor';

/**
 * Le CONTENU de l'onglet « Week-end » de l'éditeur : interrupteur « Règle
 * active », lien au groupe (« Personnaliser » / « Revenir au groupe »),
 * fieldset et WeekendEditor — et ce que chaque geste écrit dans le formulaire.
 *
 * Deux obstacles, deux contournements :
 *  - un onglet inactif n'est pas monté par Radix : `@/components/ui/tabs` est
 *    remplacé par des enveloppes qui ne montent QUE le contenu « weekend » ;
 *  - le rendu serveur ne déclenche aucun événement : l'interrupteur, les
 *    boutons et WeekendEditor sont remplacés par des doublures qui RELÈVENT
 *    leurs props, et les tests appellent leurs gestionnaires à la main. Le
 *    `setData` de useForm est un espion : une fonction de mise à jour qu'il
 *    reçoit est appliquée aux valeurs initiales pour lire le résultat.
 */

type Captured = Record<string, unknown>;

const state = vi.hoisted(() => ({
    permissions: ['yield.view', 'yield.manage'] as string[],
    setDataCalls: [] as unknown[][],
    switches: [] as Array<{ id?: string; checked?: boolean; disabled?: boolean; onCheckedChange?: (on: boolean) => void }>,
    buttons: [] as Array<{ children?: unknown; disabled?: boolean; onClick?: () => void }>,
    weekendEditors: [] as Array<{ value: unknown; onChange: (rule: unknown) => void }>,
}));

vi.mock('@inertiajs/react', () => ({
    usePage: () => ({ props: { auth: { permissions: state.permissions }, translations: {}, locale: 'fr' } }),
    useForm: <T>(initial: T) => ({
        data: initial,
        setData: (...args: unknown[]) => state.setDataCalls.push(args),
        put: vi.fn(),
        processing: false,
    }),
    router: { post: vi.fn(), put: vi.fn(), visit: vi.fn() },
}));

vi.mock('@/components/ui/tabs', async () => {
    const { createElement: h } = await import('react');
    const wrap = ({ children }: { children?: ReactNode }) => h('div', null, children);
    return {
        Tabs: wrap,
        TabsList: wrap,
        TabsTrigger: ({ children }: { children?: ReactNode }) => h('span', null, children),
        // Seul l'onglet week-end est monté : c'est lui qu'on regarde.
        TabsContent: ({ value, children }: { value: string; children?: ReactNode }) =>
            value === 'weekend' ? h('section', { 'data-tab': value }, children) : null,
    };
});

vi.mock('@/components/ui/switch', async () => {
    const { createElement: h } = await import('react');
    return {
        Switch: (props: Captured) => {
            state.switches.push(props);
            return h('button', { role: 'switch', id: props.id, 'aria-checked': String(props.checked === true), disabled: props.disabled === true });
        },
    };
});

vi.mock('@/components/ui/button', async () => {
    const { createElement: h } = await import('react');
    return {
        buttonVariants: () => '',
        Button: (props: Captured) => {
            state.buttons.push(props);
            return h('button', { disabled: props.disabled === true }, props.children as ReactNode);
        },
    };
});

vi.mock('./rules/WeekendEditor', async () => {
    const { createElement: h } = await import('react');
    return {
        default: (props: { value: unknown; onChange: (rule: unknown) => void }) => {
            state.weekendEditors.push(props);
            return h('div', { 'data-weekend-editor': JSON.stringify(props.value) });
        },
    };
});

/** Mêmes formes que YieldConfig::sectionDefaults() / sectionDisabledValues(), réduites. */
const DEFAULTS: RuleSectionValues = {
    seasonality: { base_pct: 0, seasons: [] },
    last_minute_rules: [],
    far_out_premium: { threshold_days: 60, premium_pct: 0.1 },
    oba_rules: [],
    weekend_rule: { multiplier: 1.1, days: [5, 6] },
};
const DISABLED: RuleSectionValues = {
    seasonality: { base_pct: 0, seasons: [] },
    last_minute_rules: [],
    far_out_premium: { threshold_days: 60, premium_pct: 0 },
    oba_rules: [],
    weekend_rule: { multiplier: 1, days: [5, 6] },
};

function config(overrides: Partial<YieldConfigData> = {}): YieldConfigData {
    return {
        id: 1,
        property_id: '100',
        internal_id: 100,
        property_name: 'Villa',
        is_active: true,
        min_price: 50,
        base_price: 80,
        max_price: 150,
        seasonality: { base_pct: 0, seasons: [] },
        last_minute_rules: [],
        oba_rules: [],
        far_out_premium: { threshold_days: 60, premium_pct: 0 },
        weekend_rule: { multiplier: 1, days: [5, 6] },
        rule_group_id: null,
        rule_overrides: [],
        rule_group: null,
        last_pushed_at: null,
        last_push_status: null,
        ...overrides,
    };
}

/** Un groupe lié qui porte la section week-end (×1,3 le samedi). */
const GROUP = {
    id: 7,
    name: 'Studios',
    sections: ['weekend_rule' as const],
    seasonality: null,
    last_minute_rules: null,
    far_out_premium: null,
    oba_rules: null,
    weekend_rule: { multiplier: 1.3, days: [6] },
};

/** Rend l'éditeur ; renvoie le HTML de l'onglet week-end. */
function renderWeekendTab(overrides: Partial<YieldConfigData> = {}): string {
    const html = renderToString(
        createElement(YieldRuleEditor, {
            config: config(overrides),
            obaPresets: {},
            exchangeRate: 2.9,
            sectionDefaults: DEFAULTS,
            sectionDisabled: DISABLED,
        }),
    );
    const tab = html.match(/<section data-tab="weekend">[\s\S]*<\/section>/)?.[0];
    expect(tab, 'onglet week-end non rendu').toBeDefined();
    return tab ?? '';
}

/** Le formulaire tel que l'éditeur l'initialise (les valeurs du config, normalisées). */
function formOf(overrides: Partial<YieldConfigData> = {}): Record<string, unknown> {
    const c = config(overrides);
    return {
        min_price: c.min_price,
        base_price: c.base_price,
        max_price: c.max_price,
        seasonality: c.seasonality,
        last_minute_rules: c.last_minute_rules,
        oba_rules: c.oba_rules,
        far_out_premium: c.far_out_premium,
        weekend_rule: c.weekend_rule,
        rule_overrides: c.rule_overrides,
    };
}

/** Applique le dernier setData reçu (valeur ou fonction de mise à jour) au formulaire initial. */
function lastWrite(form: Record<string, unknown>): Record<string, unknown> {
    const call = state.setDataCalls.at(-1);
    expect(call, 'aucune écriture dans le formulaire').toBeDefined();
    const [first, second] = call as unknown[];
    if (typeof first === 'function') return (first as (prev: Record<string, unknown>) => Record<string, unknown>)(form);
    return { ...form, [first as string]: second };
}

const textOf = (node: unknown): string => (typeof node === 'string' ? node : Array.isArray(node) ? node.map(textOf).join('') : '');
const buttonLabelled = (label: string) => state.buttons.find((b) => textOf(b.children).includes(label));
const weekendSwitch = () => state.switches.find((s) => String(s.id).endsWith('-weekend'));

beforeEach(() => {
    state.permissions = ['yield.view', 'yield.manage'];
    state.setDataCalls = [];
    state.switches = [];
    state.buttons = [];
    state.weekendEditors = [];
});

describe('onglet Week-end de l’éditeur — logement sans groupe', () => {
    const active = { weekend_rule: { multiplier: 1.2, days: [5, 6] } };

    it('règle réglée : interrupteur allumé et modifiable, éditeur monté avec la règle, champs actifs', () => {
        const tab = renderWeekendTab(active);

        expect(weekendSwitch()).toMatchObject({ checked: true, disabled: false });
        expect(state.weekendEditors.at(-1)?.value).toEqual({ multiplier: 1.2, days: [5, 6] });
        expect(tab).toMatch(/<fieldset(?![^>]*disabled)/);
        expect(tab).not.toContain('Règle désactivée');
        // Pas de groupe : ni « Personnaliser » ni « Revenir au groupe ».
        expect(buttonLabelled('Personnaliser')).toBeUndefined();
        expect(buttonLabelled('Revenir au groupe')).toBeUndefined();
    });

    it('une saisie dans WeekendEditor est écrite telle quelle dans weekend_rule', () => {
        renderWeekendTab(active);
        state.weekendEditors.at(-1)?.onChange({ multiplier: 0.9, days: [6] });

        expect(state.setDataCalls.at(-1)).toEqual(['weekend_rule', { multiplier: 0.9, days: [6] }]);
    });

    it('couper l’interrupteur écrit la forme NEUTRE du serveur (×1) et rien d’autre', () => {
        renderWeekendTab(active);
        weekendSwitch()?.onCheckedChange?.(false);

        const next = lastWrite(formOf(active));
        expect(next.weekend_rule).toEqual(DISABLED.weekend_rule);
        expect(next.far_out_premium).toEqual(formOf(active).far_out_premium);
    });

    it('règle neutre (défaut ×1) : interrupteur coupé, « Règle désactivée », pas d’éditeur ; le rallumer donne la suggestion ×1,10', () => {
        const tab = renderWeekendTab();

        expect(weekendSwitch()).toMatchObject({ checked: false, disabled: false });
        expect(tab).toContain('Règle désactivée');
        expect(state.weekendEditors).toHaveLength(0);

        weekendSwitch()?.onCheckedChange?.(true);
        expect(lastWrite(formOf()).weekend_rule).toEqual(DEFAULTS.weekend_rule);
    });

    it('sans yield.manage : interrupteur et champs grisés', () => {
        state.permissions = ['yield.view'];
        const tab = renderWeekendTab(active);

        expect(weekendSwitch()).toMatchObject({ checked: true, disabled: true });
        expect(tab).toMatch(/<fieldset[^>]*disabled/);
    });
});

describe('onglet Week-end de l’éditeur — logement lié à un groupe qui porte le week-end', () => {
    it('suit le groupe : valeur du groupe, interrupteur grisé, champs grisés, « Personnaliser » détache la section', () => {
        const linked = { rule_group_id: 7, rule_group: GROUP, weekend_rule: { multiplier: 1.3, days: [6] } };
        const tab = renderWeekendTab(linked);

        expect(weekendSwitch()).toMatchObject({ checked: true, disabled: true });
        expect(state.weekendEditors.at(-1)?.value).toEqual({ multiplier: 1.3, days: [6] });
        expect(tab).toMatch(/<fieldset[^>]*disabled/);

        // Grisé : le geste est sans effet.
        weekendSwitch()?.onCheckedChange?.(false);
        expect(state.setDataCalls).toHaveLength(0);

        buttonLabelled('Personnaliser')?.onClick?.();
        expect(state.setDataCalls.at(-1)).toEqual(['rule_overrides', ['weekend_rule']]);
    });

    it('détachée : « Revenir au groupe » remet la valeur du GROUPE et retire la section des personnalisations', () => {
        const detached = {
            rule_group_id: 7,
            rule_group: GROUP,
            rule_overrides: ['weekend_rule' as const],
            weekend_rule: { multiplier: 1.2, days: [5, 6] },
        };
        renderWeekendTab(detached);

        expect(weekendSwitch()).toMatchObject({ checked: true, disabled: false });
        const back = buttonLabelled('Revenir au groupe');
        expect(back).toBeDefined();
        back?.onClick?.();

        const next = lastWrite(formOf(detached));
        expect(next.weekend_rule).toEqual({ multiplier: 1.3, days: [6] });
        expect(next.rule_overrides).toEqual([]);
    });
});
