"""Synthetic-waveform replay of unchanged VoiceStudio onset policy; no models."""
import hashlib
import importlib.util
import json
from pathlib import Path
import platform
import sys
import numpy as np

source = Path(sys.argv[1])
if source.is_dir():
    source = source / 'backend/services/onset_align.py'
raw = source.read_bytes()
blob = hashlib.sha1(b'blob ' + str(len(raw)).encode() + b'\0' + raw).hexdigest()
assert blob == 'aa7e6bdf14fbd35347beabbb26797f5e1c990239', 'source differs from pinned revision'
spec = importlib.util.spec_from_file_location('voicestudio_onset_probe_source', source)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
SR = 16000

def tone(duration, amp=0.5):
    t = np.arange(round(duration * SR)) / SR
    return (amp * np.sin(2 * np.pi * 220 * t)).astype(np.float32)

def track(duration, pieces):
    audio = np.zeros(round(duration * SR), dtype=np.float32)
    for start, samples in pieces:
        at = round(start * SR)
        audio[at:at + len(samples)] += samples
    return audio

def snap(audio, separated=True):
    segments = [{'start': 0.0, 'end': len(audio) / SR, 'text': 'synthetic fixture'}]
    count = module.snap_segment_starts(segments, audio, SR, separated_vocals=separated)
    assert segments[0]['end'] == len(audio) / SR
    return {'adjusted': count, 'start': segments[0]['start'], 'end': segments[0]['end']}

print(json.dumps({'python': platform.python_version(), 'numpy': np.__version__, 'sampleRate': SR, 'sourceBlob': blob}))
clean = track(6.0, [(3.0, tone(2.0))])
clean_result, mixed_result = snap(clean), snap(clean, separated=False)
assert clean_result == {'adjusted': 1, 'start': 2.95, 'end': 6.0}
assert mixed_result == {'adjusted': 0, 'start': 0.0, 'end': 6.0}
print(json.dumps({'separated': clean_result, 'mixedSourceFlag': mixed_result}))

quiet = track(6.0, [(0.5, tone(2.0, amp=0.01)), (3.0, tone(2.0))])
quiet_onset = module.detect_speech_onset(quiet, SR, 0.0, 6.0)
quiet_result = snap(quiet)
assert quiet_onset == 3.0 and quiet_result['adjusted'] == 0
print(json.dumps({'quietEarlyTone': {'candidateOnset': quiet_onset, 'correction': quiet_result}}))

transient = np.random.default_rng(7).normal(0, 0.35, round(0.08 * SR)).astype(np.float32)
bursty = track(6.0, [(1.0, transient), (3.0, tone(2.0))])
candidate = module.detect_speech_onset(bursty, SR, 0.0, 6.0)
ticks = module.detect_speech_onsets(bursty, SR)
assert candidate == 3.0 and ticks == [1.0, 3.0]
print(json.dumps({'transientAndTone': {'correctionCandidate': candidate, 'timelineTicks': ticks}}))

near_end = track(1.0, [(0.9, tone(0.1))])
assert module.detect_speech_onset(near_end, SR, 0.0, 1.0) is None
print(json.dumps({'last100ms': {'correctionCandidate': None, 'timelineTicks': module.detect_speech_onsets(near_end, SR)}}))

frames = []
for i in range(15):
    frames.append(tone(0.02) if i % 2 == 0 else np.zeros(round(0.02 * SR), dtype=np.float32))
gapped = track(2.0, [(1.0, np.concatenate(frames))])
gapped_onset = module.detect_speech_onset(gapped, SR, 0.0, 2.0)
assert gapped_onset == 1.0
print(json.dumps({'eightNonconsecutiveFrames': {'candidateOnset': gapped_onset}}))

edge_results = {}
for duration in [0.14, 0.16]:
    audio = track(2.0, [(1.0, tone(duration))])
    onset = module.detect_speech_onset(audio, SR, 0.0, 2.0)
    assert onset == (None if duration == 0.14 else 1.0)
    edge_results[str(duration)] = onset
print(json.dumps({'toneDurationSecondsToCandidate': edge_results}))

invariants = 0
for lead in [0.0, 0.1, 0.3, 0.5, 1.0, 1.5, 2.0, 3.0]:
    for duration in [0.4, 0.8, 1.6, 2.4]:
        audio = track(lead + duration, [(lead, tone(duration))])
        result = snap(audio)
        assert 0 <= result['start'] <= result['end'] - module.MIN_SEG_DUR_S + 0.001
        assert abs(result['end'] - (lead + duration)) < 1e-12
        expected = round(lead - module.PRE_ROLL_S, 3) if lead >= 0.3 else 0.0
        assert abs(result['start'] - expected) < 0.001
        invariants += 1
print(json.dumps({'cleanToneInvariantCases': invariants, 'result': 'passed'}))
print('PASS: seven policy fixtures and 32 clean-tone invariant cases')
