import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createStore } from 'jotai';
import { UploadResponse, UploadProgress, MatrixError } from 'matrix-js-sdk';
import { createUploadAtom, UploadStatus, Upload } from './upload';
import { TUploadContent } from '../utils/matrix';

// We exercise the pure reducer inside `createUploadAtom` by driving it through a
// plain jotai store. The write atom is a state machine over UploadStatus.
//
// SKIPPED (require a real MatrixClient and/or React render, not pure logic):
//   - useBindUploadAtom (React hook; calls mx.cancelUpload, uploadContent)
//   - createUploadAtomFamily / createUploadFamilyObserverAtom (thin atomFamily
//     wrappers whose behavior is just createUploadAtom + jotai plumbing)

const makeFile = (size = 100): TUploadContent => ({ size }) as unknown as TUploadContent;

test('createUploadAtom starts in the Idle state holding the file', () => {
  const store = createStore();
  const file = makeFile();
  const uploadAtom = createUploadAtom(file);

  const state = store.get(uploadAtom);
  assert.equal(state.status, UploadStatus.Idle);
  assert.equal(state.file, file);
});

test('a promise update transitions Idle -> Loading with zeroed progress', () => {
  const store = createStore();
  const file = makeFile(2048);
  const uploadAtom = createUploadAtom(file);

  const promise = Promise.resolve({} as UploadResponse);
  store.set(uploadAtom, { promise });

  const state = store.get(uploadAtom);
  assert.equal(state.status, UploadStatus.Loading);
  if (state.status === UploadStatus.Loading) {
    assert.equal(state.promise, promise);
    assert.deepEqual(state.progress, { loaded: 0, total: 2048 });
  }
});

test('a progress update is applied only while Loading', () => {
  const store = createStore();
  const uploadAtom = createUploadAtom(makeFile(2048));

  const progress: UploadProgress = { loaded: 512, total: 2048 };

  // Ignored while Idle (not Loading).
  store.set(uploadAtom, { progress });
  assert.equal(store.get(uploadAtom).status, UploadStatus.Idle);

  // Enter Loading, then progress sticks.
  store.set(uploadAtom, { promise: Promise.resolve({} as UploadResponse) });
  store.set(uploadAtom, { progress });
  const state = store.get(uploadAtom);
  assert.equal(state.status, UploadStatus.Loading);
  if (state.status === UploadStatus.Loading) {
    assert.deepEqual(state.progress, progress);
  }
});

test('an mxc update transitions to Success', () => {
  const store = createStore();
  const uploadAtom = createUploadAtom(makeFile());

  store.set(uploadAtom, { mxc: 'mxc://example.org/abc' });
  const state = store.get(uploadAtom);
  assert.equal(state.status, UploadStatus.Success);
  if (state.status === UploadStatus.Success) {
    assert.equal(state.mxc, 'mxc://example.org/abc');
  }
});

test('an error update transitions to Error', () => {
  const store = createStore();
  const uploadAtom = createUploadAtom(makeFile());

  const error = new Error('boom') as unknown as MatrixError;
  store.set(uploadAtom, { error });
  const state = store.get(uploadAtom);
  assert.equal(state.status, UploadStatus.Error);
  if (state.status === UploadStatus.Error) {
    assert.equal(state.error, error);
  }
});

test('the file reference is preserved across transitions', () => {
  const store = createStore();
  const file = makeFile();
  const uploadAtom = createUploadAtom(file);

  const seenFiles: TUploadContent[] = [];
  const record = (s: Upload) => seenFiles.push(s.file);

  store.set(uploadAtom, { promise: Promise.resolve({} as UploadResponse) });
  record(store.get(uploadAtom));
  store.set(uploadAtom, { mxc: 'mxc://example.org/x' });
  record(store.get(uploadAtom));

  assert.ok(seenFiles.every((f) => f === file));
});
