import { afterEach, describe, expect, it, vi } from 'vitest';
import {
  collectTelegramDiag,
  insideTelegram,
  loadTelegramSDK,
  telegramSdkOutcome,
  routeExternalLinkInTelegram,
  telegramLaunch,
  telegramOpenExternalLink,
  telegramThemeParams,
  telegramSafeAreaInset,
  telegramShowSettingsButton,
  telegramDialogsAvailable,
  telegramShowConfirm,
  telegramShowPopup,
} from './telegram';

function stubWebApp(initData: string, startParam?: string) {
  vi.stubGlobal('window', {
    Telegram: {
      WebApp: {
        initData,
        initDataUnsafe: startParam ? { start_param: startParam } : {},
        themeParams: { bg_color: '#101418' },
        ready: () => {},
        expand: () => {},
      },
    },
  });
}

describe('telegram launch detection', () => {
  afterEach(() => vi.unstubAllGlobals());

  it('is not inside Telegram without a window', () => {
    expect(insideTelegram()).toBe(false);
  });

  it('is inside Telegram only with non-empty initData', () => {
    stubWebApp('');
    expect(insideTelegram()).toBe(false);
    stubWebApp('query_id=abc');
    expect(insideTelegram()).toBe(true);
  });

  it('telegramLaunch returns initData, start param and theme', () => {
    stubWebApp('query_id=abc', 'g123');
    const launch = telegramLaunch();
    expect(launch.initData).toBe('query_id=abc');
    expect(launch.startParam).toBe('g123');
    expect(launch.theme?.bg_color).toBe('#101418');
  });

  it('telegramThemeParams reads the live palette (undefined outside Telegram)', () => {
    expect(telegramThemeParams()).toBeUndefined();
    stubWebApp('query_id=abc');
    expect(telegramThemeParams()?.bg_color).toBe('#101418');
  });

  it('telegramSafeAreaInset returns zeros outside Telegram and the SDK insets inside', () => {
    expect(telegramSafeAreaInset()).toEqual({ top: 0, bottom: 0, left: 0, right: 0 });
    vi.stubGlobal('window', {
      Telegram: { WebApp: { initData: 'x', safeAreaInset: { top: 59, bottom: 34, left: 0, right: 0 } } },
    });
    expect(telegramSafeAreaInset()).toEqual({ top: 59, bottom: 34, left: 0, right: 0 });
  });
});

describe('telegramOpenExternalLink', () => {
  afterEach(() => vi.unstubAllGlobals());

  it('routes an external URL through the SDK openLink inside Telegram', () => {
    const openLink = vi.fn();
    vi.stubGlobal('window', { Telegram: { WebApp: { openLink } } });
    expect(telegramOpenExternalLink('https://gramota.ru/poisk?query=кот')).toBe(true);
    expect(openLink).toHaveBeenCalledWith('https://gramota.ru/poisk?query=кот');
  });

  it('returns false without the SDK so the caller falls back to the anchor', () => {
    expect(telegramOpenExternalLink('https://x.io')).toBe(false);
  });
});

describe('telegramShowSettingsButton', () => {
  afterEach(() => vi.unstubAllGlobals());

  it('shows the native Settings button and wires its click inside Telegram', () => {
    const onClick = vi.fn();
    const show = vi.fn();
    const handler = vi.fn();
    vi.stubGlobal('window', { Telegram: { WebApp: { SettingsButton: { onClick, show } } } });
    telegramShowSettingsButton(handler);
    expect(onClick).toHaveBeenCalledWith(handler);
    expect(show).toHaveBeenCalled();
  });

  it('is a no-op without the SDK button (older client / outside Telegram)', () => {
    expect(() => telegramShowSettingsButton(() => {})).not.toThrow();
  });
});

describe('native dialogs', () => {
  afterEach(() => vi.unstubAllGlobals());

  it('telegramDialogsAvailable reflects showPopup presence', () => {
    expect(telegramDialogsAvailable()).toBe(false);
    vi.stubGlobal('window', { Telegram: { WebApp: { showPopup: () => {} } } });
    expect(telegramDialogsAvailable()).toBe(true);
  });

  it('telegramShowConfirm resolves the user choice inside Telegram', async () => {
    vi.stubGlobal('window', {
      Telegram: { WebApp: { showConfirm: (_m: string, cb: (ok: boolean) => void) => cb(true) } },
    });
    await expect(telegramShowConfirm('Sure?')).resolves.toBe(true);
  });

  it('telegramShowConfirm resolves false without the SDK dialog', async () => {
    await expect(telegramShowConfirm('Sure?')).resolves.toBe(false);
  });

  it('telegramShowPopup resolves the pressed button id', async () => {
    vi.stubGlobal('window', {
      Telegram: { WebApp: { showPopup: (_p: unknown, cb: (id: string) => void) => cb('bot') } },
    });
    await expect(telegramShowPopup({ message: 'Hi' })).resolves.toBe('bot');
  });

  it('telegramShowPopup resolves null without the SDK', async () => {
    await expect(telegramShowPopup({ message: 'Hi' })).resolves.toBeNull();
  });
});

describe('routeExternalLinkInTelegram', () => {
  afterEach(() => vi.unstubAllGlobals());

  function stubInside(openLink = vi.fn(), origin = 'https://app.example') {
    vi.stubGlobal('window', { Telegram: { WebApp: { initData: 'query_id=abc', openLink } } });
    vi.stubGlobal('location', { href: origin + '/', origin });
    return openLink;
  }

  it('routes an external http(s) _blank link through openLink inside Telegram', () => {
    const openLink = stubInside();
    expect(routeExternalLinkInTelegram({ href: 'https://gramota.ru/poisk?query=кот', target: '_blank' })).toBe(true);
    expect(openLink).toHaveBeenCalledWith('https://gramota.ru/poisk?query=кот');
  });

  it('leaves a t.me link to the native handler (openTelegramLink owns those)', () => {
    const openLink = stubInside();
    expect(routeExternalLinkInTelegram({ href: 'https://telegram.me/some_bot', target: '_blank' })).toBe(false);
    expect(openLink).not.toHaveBeenCalled();
  });

  it('leaves same-origin and non-_blank links to in-app navigation', () => {
    const openLink = stubInside(vi.fn(), 'https://app.example');
    expect(routeExternalLinkInTelegram({ href: 'https://app.example/lobby', target: '_blank' })).toBe(false);
    expect(routeExternalLinkInTelegram({ href: 'https://gramota.ru', target: '' })).toBe(false);
    expect(openLink).not.toHaveBeenCalled();
  });

  it('does nothing outside Telegram (the anchor opens natively)', () => {
    expect(routeExternalLinkInTelegram({ href: 'https://x.io', target: '_blank' })).toBe(false);
  });
});

describe('collectTelegramDiag', () => {
  afterEach(() => vi.unstubAllGlobals());

  it('reports a missing SDK outside Telegram', () => {
    const d = collectTelegramDiag();
    expect(d.hasSDK).toBe(false);
    expect(d.hasWebApp).toBe(false);
    expect(d.initDataLen).toBe(0);
    expect(d.fieldsPresent).toEqual([]);
    expect(d.fieldsMissing).toEqual(['user', 'auth_date', 'hash', 'signature']);
  });

  it('reads field NAMES (never values) from a non-empty initData', () => {
    stubWebApp('query_id=abc&user=%7B%7D&auth_date=1&hash=deadbeef');
    const d = collectTelegramDiag();
    expect(d.hasSDK).toBe(true);
    expect(d.hasWebApp).toBe(true);
    expect(d.initDataLen).toBeGreaterThan(0);
    expect(d.fieldsPresent).toEqual(['query_id', 'user', 'auth_date', 'hash']);
    expect(d.fieldsMissing).toEqual(['signature']);
  });

  it('recovers field names from the URL fragment when the SDK left initData empty', () => {
    // Telegram passed launch data in the fragment, but WebApp.initData is empty (the Android
    // failure this screen diagnoses): the names come from the raw fragment instead, and
    // hashHadTgData flags that the data did arrive in the URL.
    vi.stubGlobal('window', { Telegram: { WebApp: { initData: '', platform: 'android' } } });
    vi.stubGlobal('location', {
      hash: '#tgWebAppData=user%3D%257B%257D%26auth_date%3D1%26hash%3Ddeadbeef&tgWebAppVersion=7.0',
      pathname: '/telegram/',
    });
    const d = collectTelegramDiag();
    expect(d.hasSDK).toBe(true);
    expect(d.platform).toBe('android');
    expect(d.initDataLen).toBe(0);
    expect(d.hashHadTgData).toBe(true);
    expect(d.fieldsPresent).toEqual(['user', 'auth_date', 'hash']);
    expect(d.fieldsMissing).toEqual(['signature']);
  });
});

describe('loadTelegramSDK', () => {
  afterEach(() => {
    vi.unstubAllGlobals();
    vi.useRealTimers();
  });

  it('resolves true and records "present" when the SDK is already there', async () => {
    vi.stubGlobal('window', { Telegram: { WebApp: { initData: '' } } });
    vi.stubGlobal('document', { createElement: vi.fn(), head: { appendChild: vi.fn() } });
    await expect(loadTelegramSDK(10000)).resolves.toBe(true);
    expect(telegramSdkOutcome()).toBe('present');
  });

  it('records "loaded" when the script defines the WebApp', async () => {
    const script: Record<string, unknown> = {};
    vi.stubGlobal('window', {});
    vi.stubGlobal('document', {
      createElement: () => script,
      head: {
        appendChild: () => {
          (window as unknown as { Telegram: unknown }).Telegram = { WebApp: { initData: '' } };
          (script.onload as () => void)();
        },
      },
    });
    await expect(loadTelegramSDK(10000)).resolves.toBe(true);
    expect(telegramSdkOutcome()).toBe('loaded');
  });

  it('records "no-webapp" when the script loads but defines nothing', async () => {
    const script: Record<string, unknown> = {};
    vi.stubGlobal('window', {});
    vi.stubGlobal('document', {
      createElement: () => script,
      head: { appendChild: () => (script.onload as () => void)() },
    });
    await expect(loadTelegramSDK(10000)).resolves.toBe(false);
    expect(telegramSdkOutcome()).toBe('no-webapp');
  });

  it('records "error" when the script fails to load (telegram.org unreachable)', async () => {
    const script: Record<string, unknown> = {};
    vi.stubGlobal('window', {});
    vi.stubGlobal('document', {
      createElement: () => script,
      head: { appendChild: () => (script.onerror as () => void)() },
    });
    await expect(loadTelegramSDK(10000)).resolves.toBe(false);
    expect(telegramSdkOutcome()).toBe('error');
  });

  it('records "timeout" when the script neither loads nor fails in time', async () => {
    vi.useFakeTimers();
    vi.stubGlobal('window', {});
    vi.stubGlobal('document', { createElement: () => ({}), head: { appendChild: vi.fn() } });
    const p = loadTelegramSDK(10000);
    await vi.advanceTimersByTimeAsync(10000);
    await expect(p).resolves.toBe(false);
    expect(telegramSdkOutcome()).toBe('timeout');
  });
});
