import 'dart:math' as math;
import 'dart:ui';

import 'package:flutter_test/flutter_test.dart';
import 'package:qlyra/frontend/widgets/attachment/bubbles/video_note_bubble.dart';

const _extent = 210.0;
const _knob = 7.0;
const _geometry = NoteRingGeometry(extent: _extent, knobRadius: _knob);

void main() {
  test('ручка идёт по ободу от 12 часов по часовой', () {
    final center = _geometry.center;
    final r = _geometry.radius;

    expect(_geometry.knobCenter(0).dx, closeTo(center.dx, 0.001));
    expect(_geometry.knobCenter(0).dy, closeTo(center.dy - r, 0.001));

    expect(_geometry.knobCenter(0.25).dx, closeTo(center.dx + r, 0.001));
    expect(_geometry.knobCenter(0.25).dy, closeTo(center.dy, 0.001));

    expect(_geometry.knobCenter(0.5).dy, closeTo(center.dy + r, 0.001));
    expect(_geometry.knobCenter(0.75).dx, closeTo(center.dx - r, 0.001));

    expect(_geometry.knobCenter(1).dx, closeTo(center.dx, 0.001));
    expect(_geometry.knobCenter(1).dy, closeTo(center.dy - r, 0.001));
  });

  test('ручка целиком помещается в бокс и не обрезается', () {
    for (var i = 0; i <= 100; i++) {
      final knob = _geometry.knobCenter(i / 100);
      expect(knob.dx - _knob, greaterThanOrEqualTo(0));
      expect(knob.dy - _knob, greaterThanOrEqualTo(0));
      expect(knob.dx + _knob, lessThanOrEqualTo(_extent));
      expect(knob.dy + _knob, lessThanOrEqualTo(_extent));
    }
  });

  test('центр кружка остаётся под тап, обод и ручка ловят драг', () {
    expect(
      _geometry.grabs(_geometry.center, 0),
      isFalse,
      reason: 'центр должен переключать воспроизведение, а не мотать',
    );
    expect(_geometry.grabs(_geometry.knobCenter(0.4), 0.4), isTrue);
    expect(
      _geometry.grabs(_geometry.knobCenter(0.4) + const Offset(0, 18), 0.4),
      isTrue,
      reason: 'промах мимо ручки в пределах допуска всё ещё считается',
    );

    final onBand = _geometry.center + Offset(_geometry.radius, 0);
    expect(_geometry.grabs(onBand, 0), isTrue);

    final wellInside = _geometry.center + Offset(_geometry.radius / 2, 0);
    expect(_geometry.grabs(wellInside, 0), isFalse);
  });

  test('тап по ободу попадает ровно в свою долю', () {
    for (final progress in [0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 0.999]) {
      expect(
        _geometry.progressAt(_geometry.knobCenter(progress)),
        closeTo(progress, 0.0001),
        reason: 'тап по точке $progress',
      );
    }
  });

  test('тап работает на любом удалении от обода вдоль того же луча', () {
    final center = _geometry.center;
    for (final radius in [_geometry.radius - 10, _geometry.radius + 5]) {
      final point = center + Offset(radius, 0);
      expect(_geometry.progressAt(point), closeTo(0.25, 0.0001));
    }
  });

  test('захват абсолютный: драг ведёт ручку ровно под пальцем', () {
    for (final route in [
      [0.1, 0.3],
      [0.4, 0.2],
      [0.7, 0.95],
      [0.0, 0.15],
    ]) {
      final from = _geometry.knobCenter(route[0]);
      final to = _geometry.knobCenter(route[1]);

      final grabbed = _geometry.progressAt(from);
      expect(
        grabbed,
        closeTo(route[0], 1e-4),
        reason: 'захват должен встать в точку пальца, а не в текущую позицию',
      );

      final delta = NoteRingGeometry.angleDelta(
        _geometry.angleAt(from),
        _geometry.angleAt(to),
      );
      expect(
        _geometry.advance(grabbed, delta),
        closeTo(route[1], 1e-4),
        reason: 'ручка отстала от пальца на маршруте $route',
      );
    }
  });

  test('прокрутка мимо конца упирается, а не заворачивается в начало', () {
    final from = _geometry.knobCenter(0.9);
    final to = _geometry.knobCenter(0.1);
    final delta = NoteRingGeometry.angleDelta(
      _geometry.angleAt(from),
      _geometry.angleAt(to),
    );
    expect(delta, greaterThan(0));
    expect(_geometry.advance(_geometry.progressAt(from), delta), 1.0);
  });

  test('переход через 12 часов не перебрасывает позицию', () {
    final before = _geometry.angleAt(_geometry.center + const Offset(-4, -90));
    final after = _geometry.angleAt(_geometry.center + const Offset(4, -90));
    final delta = NoteRingGeometry.angleDelta(before, after);

    expect(delta.abs(), lessThan(0.3), reason: 'скачок вместо плавного шага');
    expect(delta, greaterThan(0));
    expect(_geometry.advance(0.99, delta), 1.0);
    expect(_geometry.advance(0.01, -delta), greaterThanOrEqualTo(0.0));
  });

  test('прогресс зажат в границах при бесконечной прокрутке', () {
    expect(_geometry.advance(0.5, 100 * math.pi), 1.0);
    expect(_geometry.advance(0.5, -100 * math.pi), 0.0);
  });

  test('увеличенный кружок сохраняет пропорции обода', () {
    const big = NoteRingGeometry(extent: 357, knobRadius: 9);
    expect(big.radius, closeTo(357 / 2 - 10, 0.001));
    expect(big.knobCenter(0).dy, closeTo(big.center.dy - big.radius, 0.001));
    expect(big.knobCenter(0.5).dy + 9, lessThanOrEqualTo(357));
  });
}
