import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
  bytesToSize,
  millisecondsToMinutesAndSeconds,
  millisecondsToMinutes,
  secondsToMinutesAndSeconds,
  binarySearch,
  randomNumberBetween,
  scaleYDimension,
  parseGeoUri,
  trimLeadingSlash,
  trimTrailingSlash,
  trimSlash,
  nameInitials,
  randomStr,
  suffixRename,
  replaceSpaceWithDash,
  splitWithSpace,
  fulfilledPromiseSettledResult,
  promiseFulfilledResult,
  promiseRejectedResult,
} from './common';

test('bytesToSize', () => {
  assert.equal(bytesToSize(0), '0KB');
  assert.equal(bytesToSize(500), '0.5 KB'); // sub-KB is floored up to the KB unit
  assert.equal(bytesToSize(1500), '1.5 KB');
  assert.equal(bytesToSize(1_500_000), '1.5 MB');
  assert.equal(bytesToSize(1_500_000_000), '1.5 GB');
});

test('millisecondsToMinutesAndSeconds zero-pads seconds', () => {
  assert.equal(millisecondsToMinutesAndSeconds(0), '0:00');
  assert.equal(millisecondsToMinutesAndSeconds(5000), '0:05');
  assert.equal(millisecondsToMinutesAndSeconds(65_000), '1:05');
  assert.equal(millisecondsToMinutesAndSeconds(125_000), '2:05');
});

test('millisecondsToMinutes floors to whole minutes', () => {
  assert.equal(millisecondsToMinutes(0), '0');
  assert.equal(millisecondsToMinutes(59_000), '0');
  assert.equal(millisecondsToMinutes(125_000), '2');
});

test('secondsToMinutesAndSeconds', () => {
  assert.equal(secondsToMinutesAndSeconds(5), '0:05');
  assert.equal(secondsToMinutesAndSeconds(65), '1:05');
  assert.equal(secondsToMinutesAndSeconds(90), '1:30');
});

test('binarySearch finds and misses on an ascending array', () => {
  const arr = [1, 3, 5, 7, 9];
  const matcher = (target: number) => (item: number) =>
    item === target ? 0 : item > target ? 1 : -1;
  assert.equal(binarySearch(arr, matcher(7)), 7);
  assert.equal(binarySearch(arr, matcher(1)), 1);
  assert.equal(binarySearch(arr, matcher(9)), 9);
  assert.equal(binarySearch(arr, matcher(4)), undefined);
  assert.equal(binarySearch([], matcher(1)), undefined);
});

test('randomNumberBetween stays within inclusive bounds', () => {
  for (let i = 0; i < 1000; i += 1) {
    const n = randomNumberBetween(3, 7);
    assert.ok(n >= 3 && n <= 7, `${n} out of [3,7]`);
    assert.equal(Number.isInteger(n), true);
  }
  assert.equal(randomNumberBetween(5, 5), 5);
});

test('scaleYDimension preserves aspect ratio', () => {
  assert.equal(scaleYDimension(100, 50, 200), 100);
  assert.equal(scaleYDimension(200, 200, 80), 80);
});

test('parseGeoUri', () => {
  assert.deepEqual(parseGeoUri('geo:37.7,-122.4'), { latitude: '37.7', longitude: '-122.4' });
  assert.deepEqual(parseGeoUri('geo:37.7,-122.4;u=10'), {
    latitude: '37.7',
    longitude: '-122.4',
  });
  assert.equal(parseGeoUri('not-a-geo-uri'), undefined);
  assert.equal(parseGeoUri(''), undefined);
});

test('slash trimmers', () => {
  assert.equal(trimLeadingSlash('///a/b'), 'a/b');
  assert.equal(trimTrailingSlash('a/b///'), 'a/b');
  assert.equal(trimSlash('//a/b//'), 'a/b');
  assert.equal(trimSlash('a/b'), 'a/b');
});

test('nameInitials', () => {
  assert.equal(nameInitials('Alice'), 'A');
  assert.equal(nameInitials('alice'), 'a');
  assert.equal(nameInitials('Alice Bob', 2), 'Al'); // takes characters, not words
  // empty/nullish all collapse to the same single-char fallback
  const fallback = nameInitials('');
  assert.equal(fallback.length, 1);
  assert.equal(nameInitials(null), fallback);
  assert.equal(nameInitials(undefined), fallback);
});

test('randomStr length and charset', () => {
  const allowed = /^[A-Za-z0-9]+$/;
  assert.equal(randomStr(12).length, 12);
  assert.equal(randomStr(1).length, 1);
  assert.match(randomStr(32), allowed);
});

test('suffixRename increments until the validator rejects', () => {
  // validator returns true while the candidate is still "taken"
  assert.equal(
    suffixRename('file', (n) => n === 'file1'),
    'file2',
  );
  assert.equal(
    suffixRename('doc', () => false),
    'doc1',
  );
});

test('replaceSpaceWithDash', () => {
  assert.equal(replaceSpaceWithDash('a b c'), 'a-b-c');
  assert.equal(replaceSpaceWithDash('nospaces'), 'nospaces');
});

test('splitWithSpace trims and drops empties', () => {
  assert.deepEqual(splitWithSpace('  a b  '), ['a', 'b']);
  assert.deepEqual(splitWithSpace('   '), []);
  assert.deepEqual(splitWithSpace('hello'), ['hello']);
});

test('promise settled-result helpers', () => {
  const settled: PromiseSettledResult<number>[] = [
    { status: 'fulfilled', value: 1 },
    { status: 'rejected', reason: 'boom' },
    { status: 'fulfilled', value: 2 },
  ];
  assert.deepEqual(fulfilledPromiseSettledResult(settled), [1, 2]);
  assert.equal(promiseFulfilledResult(settled[0]), 1);
  assert.equal(promiseFulfilledResult(settled[1]), undefined);
  assert.equal(promiseRejectedResult(settled[1]), 'boom');
  assert.equal(promiseRejectedResult(settled[0]), undefined);
});
