import { describe, it, expect } from 'vitest';
import { z } from 'zod';
import {
    digitsOnly,
    formatYear,
    parseUserNumber,
    parseUserNumberClamped,
    persianSafeNumber,
    iranNationalCodeSchema,
    iranNationalCodeOptionalSchema,
    formatRial,
    rialToToman,
    tomanToRial,
    formatNumber,
    toDate,
    formatDate,
    formatDateTime,
    toGregorianDate,
    toTehranClockTime,
    tehranWallClockToUnix,
    unixToIso,
    todayIso,
    maskMobile,
    cn,
    toEnglishDigits,
    sanitizeMobileInput,
    isValidIranMobile,
    IRAN_MOBILE_RE,
    iranMobileSchema,
    iranMobileOptionalSchema,
} from '@/lib/utils';

const PERSIAN_DIGITS = /[۰-۹]/;

describe('formatRial', () => {
    it('مقدار ریال را ÷۱۰ به تومان تبدیل و با پسوند تومان برمی‌گرداند', () => {
        const out = formatRial(150000); // ۱۵۰٬۰۰۰ ریال = ۱۵٬۰۰۰ تومان
        expect(out).toContain('تومان');
        expect(out).toMatch(PERSIAN_DIGITS);
        expect(out).toContain(new Intl.NumberFormat('fa-IR').format(15000));
    });
    it('صفر را هم فرمت می‌کند', () => {
        expect(formatRial(0)).toContain('تومان');
    });
});

describe('rialToToman / tomanToRial', () => {
    it('ریال↔تومان با ضریب ۱۰', () => {
        expect(rialToToman(150000)).toBe(15000);
        expect(tomanToRial(15000)).toBe(150000);
        expect(tomanToRial(rialToToman(500))).toBe(500);
    });
});

describe('formatNumber', () => {
    it('رقم فارسی برمی‌گرداند', () => {
        expect(formatNumber(1234)).toMatch(PERSIAN_DIGITS);
    });
});

describe('toDate', () => {
    it('null و رشته خالی → null', () => {
        expect(toDate(null)).toBeNull();
        expect(toDate(undefined)).toBeNull();
        expect(toDate('')).toBeNull();
    });
    it('عدد را به‌عنوان ثانیه (×۱۰۰۰) تفسیر می‌کند', () => {
        const d = toDate(1_700_000_000);
        expect(d).toBeInstanceOf(Date);
        expect(d!.getTime()).toBe(1_700_000_000 * 1000);
    });
    it('Y-m-d را ظهر محلی می‌گیرد (نه نیمه‌شب UTC)', () => {
        const d = toDate('2024-03-21');
        expect(d!.getHours()).toBe(12);
        expect(d!.getFullYear()).toBe(2024);
    });
    it('رشته ISO کامل را پارس می‌کند', () => {
        const d = toDate('2024-03-21T08:30:00Z');
        expect(d).toBeInstanceOf(Date);
        expect(isNaN(d!.getTime())).toBe(false);
    });
});

describe('formatDate', () => {
    it('ورودی نامعتبر → "—"', () => {
        expect(formatDate(null)).toBe('—');
        expect(formatDate('not-a-date')).toBe('—');
    });
    it('timestamp ثانیه‌ای معتبر → رشته شمسی با رقم فارسی', () => {
        const out = formatDate(1_700_000_000);
        expect(out).not.toBe('—');
        expect(out).toMatch(PERSIAN_DIGITS);
    });
});

describe('formatDateTime', () => {
    it('ورودی نامعتبر → "—"', () => {
        expect(formatDateTime(undefined)).toBe('—');
    });
    it('معتبر → شامل ساعت و رقم فارسی', () => {
        const out = formatDateTime(1_700_000_000);
        expect(out).not.toBe('—');
        expect(out).toMatch(PERSIAN_DIGITS);
    });
});

describe('toGregorianDate', () => {
    it('YYYY-MM-DD با صفر پیش‌رو', () => {
        expect(toGregorianDate(new Date(2024, 0, 5))).toBe('2024-01-05');
        expect(toGregorianDate(new Date(2024, 11, 31))).toBe('2024-12-31');
    });
});

describe('unixToIso / todayIso / toTehranClockTime', () => {
    // نیمه‌شبِ ۲۹ ژوئیهٔ ۲۰۲۶ به وقت تهران = ۲۸ ژوئیه ۲۰:۳۰ UTC. تبدیل با
    // toISOString همین‌جا یک روز عقب می‌رفت — رگرسیونِ «تاریخ خاص یک روز قبل».
    const tehranMidnight = Math.floor(Date.UTC(2026, 6, 28, 20, 30, 0) / 1000);

    it('timestamp نیمه‌شبِ تهران همان روز را می‌دهد، نه روز قبل', () => {
        expect(unixToIso(tehranMidnight)).toBe('2026-07-29');
    });

    it('لحظهٔ آخر شب به وقت تهران هنوز همان روز است', () => {
        const lateNight = Math.floor(Date.UTC(2026, 6, 29, 20, 0, 0) / 1000); // ۲۳:۳۰ تهران
        expect(unixToIso(lateNight)).toBe('2026-07-29');
    });

    it('مقدار خالی رشتهٔ تهی می‌دهد', () => {
        expect(unixToIso(null)).toBe('');
        expect(unixToIso(undefined)).toBe('');
    });

    it('todayIso همان تاریخ تهرانِ لحظهٔ جاری است', () => {
        expect(todayIso()).toBe(toGregorianDate(new Date()));
        expect(todayIso()).toMatch(/^\d{4}-\d{2}-\d{2}$/);
    });

    it('ساعت دیواری تهران با رقم لاتین و ۲۴ساعته برمی‌گردد', () => {
        expect(toTehranClockTime(new Date(tehranMidnight * 1000))).toBe('00:00');
        expect(toTehranClockTime(new Date(Date.UTC(2026, 6, 29, 10, 0, 0)))).toBe('13:30');
    });

    it('رفت‌وبرگشتِ tehranWallClockToUnix ↔ unixToIso روز را جابه‌جا نمی‌کند', () => {
        const ts = tehranWallClockToUnix('2026-07-29', '00:00');
        expect(ts).toBe(tehranMidnight);
        expect(unixToIso(ts)).toBe('2026-07-29');
    });
});

describe('maskMobile', () => {
    it('شماره ۱۱ رقمی را ماسک می‌کند', () => {
        expect(maskMobile('09123456789')).toBe('0912***789');
    });
    it('شماره کوتاه‌تر از ۷ رقم بدون تغییر', () => {
        expect(maskMobile('123')).toBe('123');
    });
});

describe('cn', () => {
    it('مقادیر falsy را حذف و بقیه را با فاصله می‌چسباند', () => {
        expect(cn('a', false, null, undefined, 'b')).toBe('a b');
        expect(cn()).toBe('');
    });
});

describe('toEnglishDigits', () => {
    it('رقم فارسی به انگلیسی', () => {
        expect(toEnglishDigits('۰۹۱۲۳۴۵۶۷۸۹')).toBe('09123456789');
    });
    it('رقم عربی به انگلیسی', () => {
        expect(toEnglishDigits('٠٩١٢')).toBe('0912');
    });
    it('ورودی خالی → رشته خالی', () => {
        expect(toEnglishDigits('')).toBe('');
    });
    it('کاراکترهای غیررقمی را دست‌نخورده نگه می‌دارد', () => {
        expect(toEnglishDigits('شماره ۰۹')).toBe('شماره 09');
    });
});

describe('digitsOnly', () => {
    it('رقم فارسی و عربی مخلوط را نرمال و غیررقم را حذف می‌کند', () => {
        expect(digitsOnly('۱۲٣٤-56 ب')).toBe('123456');
    });
    it('با maxLen برش می‌زند', () => {
        expect(digitsOnly('۱۲۳۴۵۶۷۸۹۰۱۲', 10)).toBe('1234567890');
    });
    it('ورودی خالی → رشته خالی', () => {
        expect(digitsOnly('')).toBe('');
    });
    it('فقط حروف → خالی', () => {
        expect(digitsOnly('کد ملی')).toBe('');
    });
});

describe('formatYear', () => {
    it('سال شمسی را بدون جداکننده‌ی هزارگان می‌دهد', () => {
        expect(formatYear(1404)).toBe('۱۴۰۴');
    });
    it('برخلاف formatNumber جداکننده نمی‌گذارد', () => {
        expect(formatNumber(1404)).not.toBe(formatYear(1404));
    });
});

describe('parseUserNumber', () => {
    it('رقم فارسی را به عدد تبدیل می‌کند', () => {
        expect(parseUserNumber('۲۵')).toBe(25);
    });
    it('رقم عربی را به عدد تبدیل می‌کند', () => {
        expect(parseUserNumber('٢٥')).toBe(25);
    });
    it('جداکننده هزارگان و فاصله را نادیده می‌گیرد', () => {
        expect(parseUserNumber('1,200')).toBe(1200);
        expect(parseUserNumber('۸۵٬۰۰۰')).toBe(85000);
        expect(parseUserNumber(' ۹۰ ')).toBe(90);
    });
    it('اعشار فارسی را می‌پذیرد', () => {
        expect(parseUserNumber('۱۲.۵')).toBe(12.5);
    });
    it('عدد منفی را می‌پذیرد', () => {
        expect(parseUserNumber('-۳')).toBe(-3);
    });
    it('عدد را دست‌نخورده رد می‌کند', () => {
        expect(parseUserNumber(42)).toBe(42);
    });
    it('خالی و null → null', () => {
        expect(parseUserNumber('')).toBeNull();
        expect(parseUserNumber(null)).toBeNull();
        expect(parseUserNumber(undefined)).toBeNull();
    });
    it('ورودی نامعتبر → null و هرگز NaN', () => {
        expect(parseUserNumber('abc')).toBeNull();
        expect(parseUserNumber('۱۲ب۳')).toBeNull();
        expect(parseUserNumber('.')).toBeNull();
    });
});

describe('parseUserNumberClamped', () => {
    it('مقدار داخل بازه را دست‌نخورده برمی‌گرداند', () => {
        expect(parseUserNumberClamped('۲۵', 0, 100)).toBe(25);
    });
    it('بیشتر از سقف را به سقف می‌برد', () => {
        expect(parseUserNumberClamped('۱۵۰', 0, 100)).toBe(100);
    });
    it('کمتر از کف را به کف می‌برد', () => {
        expect(parseUserNumberClamped('-۵', 0, 100)).toBe(0);
    });
    it('ورودی نامعتبر → null (نه کف)', () => {
        expect(parseUserNumberClamped('abc', 0, 100)).toBeNull();
        expect(parseUserNumberClamped('', 0, 100)).toBeNull();
    });
});

describe('persianSafeNumber', () => {
    it('رشته‌ی فارسی را قبل از عدد شدن نرمال می‌کند', () => {
        const schema = persianSafeNumber(z.coerce.number());
        expect(schema.parse('۱۲۳')).toBe(123);
    });
    it('عدد را دست‌نخورده رد می‌کند', () => {
        const schema = persianSafeNumber(z.coerce.number());
        expect(schema.parse(42)).toBe(42);
    });
});

describe('iranNationalCodeSchema', () => {
    it('کد ملی فارسی را می‌پذیرد و لاتین برمی‌گرداند', () => {
        expect(iranNationalCodeSchema.parse('۰۰۱۲۳۴۵۶۷۸')).toBe('0012345678');
    });
    it('کمتر از ۱۰ رقم رد می‌شود', () => {
        expect(() => iranNationalCodeSchema.parse('12345')).toThrow();
    });
    it('نسخه‌ی اختیاری خالی را می‌پذیرد', () => {
        expect(iranNationalCodeOptionalSchema.parse('')).toBe('');
        expect(() => iranNationalCodeOptionalSchema.parse('123')).toThrow();
    });
});

describe('sanitizeMobileInput', () => {
    it('غیررقم حذف، رقم فارسی نرمال، حداکثر ۱۱ رقم', () => {
        expect(sanitizeMobileInput('۰۹۱۲-۳۴۵ ۶۷۸۹۰۱۲')).toBe('09123456789');
    });
    it('فقط حروف → خالی', () => {
        expect(sanitizeMobileInput('abc')).toBe('');
    });
});

describe('isValidIranMobile / IRAN_MOBILE_RE', () => {
    it.each([
        ['09123456789', true],
        ['۰۹۱۲۳۴۵۶۷۸۹', true],
        ['9123456789', false],
        ['08123456789', false],
        ['0912345678', false],
        ['091234567890', false],
    ])('%s → %s', (input, expected) => {
        expect(isValidIranMobile(input as string)).toBe(expected);
    });
    it('regex مستقیم روی ارقام انگلیسی', () => {
        expect(IRAN_MOBILE_RE.test('09120000000')).toBe(true);
    });
});

describe('iranMobileSchema', () => {
    it('رقم فارسی را پذیرفته و نرمال می‌کند', () => {
        expect(iranMobileSchema.parse('۰۹۱۲۳۴۵۶۷۸۹')).toBe('09123456789');
    });
    it('جداکننده‌ها را حذف می‌کند', () => {
        expect(iranMobileSchema.parse('0912-345-6789')).toBe('09123456789');
    });
    it('شماره نامعتبر → ناموفق', () => {
        const r = iranMobileSchema.safeParse('12345');
        expect(r.success).toBe(false);
    });
});

describe('iranMobileOptionalSchema', () => {
    it('رشته خالی مجاز است', () => {
        expect(iranMobileOptionalSchema.parse('')).toBe('');
    });
    it('شماره معتبر نرمال می‌شود', () => {
        expect(iranMobileOptionalSchema.parse('۰۹۱۲۳۴۵۶۷۸۹')).toBe('09123456789');
    });
    it('شماره نیمه‌کاره → ناموفق', () => {
        expect(iranMobileOptionalSchema.safeParse('0912').success).toBe(false);
    });
});
