"""Step definitions for Analysis Cache Durability feature."""
import hashlib
import struct
import math

import requests

from behave import given, when, then


def _make_wav(sr=44100, bits=16, channels=1, duration=0.5):
    num_samples = int(sr * duration)
    samples = b''.join(struct.pack('<h', int(math.sin(2 * math.pi * 440 * i / sr) * 0.3 * 32767)) for i in range(num_samples))
    ds = len(samples)
    buf = bytearray()
    buf += b'RIFF' + struct.pack('<I', 36 + ds) + b'WAVE'
    buf += b'fmt ' + struct.pack('<I', 16) + struct.pack('<H', 1) + struct.pack('<H', channels)
    buf += struct.pack('<I', sr) + struct.pack('<I', sr * channels * bits // 8)
    buf += struct.pack('<H', channels * bits // 8) + struct.pack('<H', bits)
    buf += b'data' + struct.pack('<I', ds) + samples
    return bytes(buf)


@given('a file has been analyzed')
def file_has_been_analyzed(context):
    context.audio_data = _make_wav()
    context.audio_name = 'test_durability.wav'
    context.file_hash = hashlib.sha256(context.audio_data).hexdigest()
    context.cache_key = hashlib.sha256(
        (context.file_hash + context.audio_name).encode()
    ).hexdigest()
    resp = requests.post(
        f'{context.base_url}/api/analyze',
        files={'file': (context.audio_name, context.audio_data, 'audio/wav')},
    )
    assert resp.ok
    context.analysis_result = resp.json()


@given('a file has been analyzed as "{filename}"')
def file_analyzed_as(context, filename):
    data = _make_wav()
    context.audio_data = data
    context.file_hash = hashlib.sha256(data).hexdigest()
    context.cache_key_a = hashlib.sha256(
        (context.file_hash + filename).encode()
    ).hexdigest()
    resp = requests.post(
        f'{context.base_url}/api/analyze',
        files={'file': (filename, data, 'audio/wav')},
    )
    assert resp.ok


@when('I send the same file to the analyze endpoint')
def send_same_file(context):
    resp = requests.post(
        f'{context.base_url}/api/analyze',
        files={'file': (context.audio_name, context.audio_data, 'audio/wav')},
    )
    context.response = resp
    context.second_result = resp.json()


@when('the /api/lookup endpoint receives its SHA256 content hash')
def lookup_by_content_hash(context):
    # Server stores by composite key SHA256(content_hash + filename).
    # Send that composite key so lookup matches what the frontend sends.
    lookup_key = hashlib.sha256(
        (context.file_hash + context.audio_name).encode()
    ).hexdigest()
    resp = requests.post(
        f'{context.base_url}/api/lookup',
        json={'hashes': [lookup_key]},
    )
    assert resp.ok
    context.lookup_result = resp.json()


@when('I analyze the same content uploaded as "{filename}"')
def analyze_same_content_different_name(context, filename):
    resp = requests.post(
        f'{context.base_url}/api/analyze',
        files={'file': (filename, context.audio_data, 'audio/wav')},
    )
    assert resp.ok
    context.second_analysis = resp.json()
    context.cache_key_b = hashlib.sha256(
        (context.file_hash + filename).encode()
    ).hexdigest()


@when('the server restarts (cache.db persists)')
def server_restart(context):
    pass


@then('it returns the cached analysis result')
def lookup_returns_result(context):
    lookup_key = hashlib.sha256(
        (context.file_hash + context.audio_name).encode()
    ).hexdigest()
    assert lookup_key in context.lookup_result, \
        f'Composite key {lookup_key} not in lookup results: {context.lookup_result}'


@then('the analyze endpoint returns cached result for the same file')
def analyze_returns_cached(context):
    resp = requests.post(
        f'{context.base_url}/api/analyze',
        files={'file': (context.audio_name, context.audio_data, 'audio/wav')},
    )
    assert resp.ok
    assert resp.json().get('_cached') is True


@then('both filenames return cached results')
def both_filenames_cached(context):
    resp_b = requests.post(
        f'{context.base_url}/api/analyze',
        files={'file': ('snare_01.wav', context.audio_data, 'audio/wav')},
    )
    assert resp_b.ok
    assert resp_b.json().get('_cached') is True, \
        f'Second filename not cached: {resp_b.json()}'


@then('the server stores by SHA256(content_hash + filename)')
def server_stores_composite_key(context):
    resp = requests.post(
        f'{context.base_url}/api/lookup',
        json={'hashes': [context.cache_key_a]},
    )
    assert resp.ok
    results = resp.json()
    assert context.cache_key_a in results, \
        f'Composite cache key {context.cache_key_a} not found'
