#!/bin/bash
#=============================================================================
# ods-test-functional.sh - Functional Testing for ODS
#
# Tests actual functionality, not just port availability:
# - LLM (llama-server) generates coherent text
# - Whisper transcribes actual audio
# - TTS generates valid audio files
# - Embeddings produce vectors
#
# This complements ods-test.sh which checks service health.
#=============================================================================

# Require Bash 4+ (associative arrays used for service-port lookup)
if (( BASH_VERSINFO[0] < 4 )); then
    echo "ERROR: $(basename "$0") requires Bash 4.0+ (you have $BASH_VERSION)" >&2
    echo "  macOS ships Bash 3.2 due to licensing. Install a modern version:" >&2
    echo "    brew install bash" >&2
    exit 1
fi

set -euo pipefail

# Colors
RED='\e[0;31m'
GREEN='\e[0;32m'
YELLOW='\e[1;33m'
NC='\e[0m'

# Source service registry for port resolution
_FT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if [[ -f "$_FT_DIR/lib/service-registry.sh" ]]; then
    export SCRIPT_DIR="$_FT_DIR"
    . "$_FT_DIR/lib/service-registry.sh"
    sr_load
    [[ -f "$_FT_DIR/lib/safe-env.sh" ]] && . "$_FT_DIR/lib/safe-env.sh"
    load_env_file "$_FT_DIR/.env"
    sr_resolve_ports
fi

# Ensure SERVICE_PORTS is declared even if service-registry.sh was not sourced
declare -A SERVICE_PORTS

# Service endpoints — resolved from registry
LLM_URL="${LLM_URL:-http://localhost:${SERVICE_PORTS[llama-server]:-11434}}"
WHISPER_URL="${WHISPER_URL:-http://localhost:${SERVICE_PORTS[whisper]:-9000}}"
TTS_URL="${TTS_URL:-http://localhost:${SERVICE_PORTS[tts]:-8880}}"
EMBEDDING_URL="${EMBEDDING_URL:-http://localhost:${SERVICE_PORTS[embeddings]:-9103}}"

# Test tracking
TESTS_PASSED=0
TESTS_FAILED=0

pass() {
    echo -e "${GREEN}✓${NC} $1"
    # Arithmetic expansion (not `((...))`) — the compound form returns
    # exit 1 when the pre-increment value is 0, which would trip `set -e`
    # on the first pass/fail and abort the script before any summary.
    TESTS_PASSED=$((TESTS_PASSED + 1))
}

fail() {
    echo -e "${RED}✗${NC} $1"
    TESTS_FAILED=$((TESTS_FAILED + 1))
}

warn() {
    echo -e "${YELLOW}⚠${NC} $1"
}

# Test 1: LLM generates coherent text
test_llm_functional() {
    echo ""
    echo "> Testing LLM Functional Generation"

    # The grep-based extraction pipelines in this script may legitimately
    # produce zero matches (LLM/TTS/embeddings/whisper offline or returning
    # unexpected payload). Under `set -euo pipefail` such a no-match would
    # abort the script before the `[[ -z ... ]]` guard below can treat it
    # as a test failure. Using `if ! VAR=$(...)` keeps the set -e safety
    # net engaged everywhere else; `set -e` is disabled only for the
    # evaluation of the condition (per bash spec), so a failed pipeline
    # leaves VAR empty and the explicit `fail` path below runs.
    local model_id=""
    if ! model_id=$(curl -s --max-time 10 "$LLM_URL/v1/models" 2>/dev/null | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4); then
        model_id=""
    fi
    model_id="${model_id:-local}"

    local prompt="What is 2+2? Answer with just the number."
    local payload="{\"model\": \"$model_id\", \"messages\": [{\"role\": \"user\", \"content\": \"$prompt\"}], \"max_tokens\": 10, \"temperature\": 0.1}"

    local response
    response=$(curl -s --max-time 30 \
        -X POST "$LLM_URL/v1/chat/completions" \
        -H "Content-Type: application/json" \
        -d "$payload" 2>/dev/null || echo "")

    if [[ -z "$response" ]]; then
        fail "LLM returned no response"
        return 1
    fi

    local content=""
    if ! content=$(echo "$response" | grep -oE '"content":[[:space:]]*"[^"]+"' | head -1 | cut -d'"' -f4); then
        content=""
    fi

    if [[ -z "$content" ]]; then
        fail "LLM returned empty content"
        return 1
    fi

    # Check if response contains "4" (the answer to 2+2)
    if echo "$content" | grep -q "4"; then
        pass "LLM generates correct answer: '$content'"
    else
        warn "LLM generated: '$content' (expected '4')"
        pass "LLM generates text (answer may vary)"
    fi
}

# Test 2: TTS generates valid audio file
test_tts_functional() {
    echo ""
    echo "> Testing TTS Audio Generation"
    
    local test_text="Hello, this is a test."
    local output_file="/tmp/test_tts_output.wav"
    
    local payload="{\"model\": \"kokoro\", \"input\": \"$test_text\", \"voice\": \"af_bella\", \"response_format\": \"wav\"}"
    
    # Generate audio
    local http_code
    http_code=$(curl -s -w "%{http_code}" --max-time 30 \
        -X POST "$TTS_URL/v1/audio/speech" \
        -H "Content-Type: application/json" \
        -d "$payload" \
        -o "$output_file" 2>/dev/null)
    
    if [[ "$http_code" != "200" ]]; then
        fail "TTS returned HTTP $http_code"
        rm -f "$output_file"
        return 1
    fi
    
    # Check file exists and has content
    if [[ ! -f "$output_file" ]]; then
        fail "TTS did not create output file"
        return 1
    fi
    
    local file_size
    file_size=$(stat -c%s "$output_file" 2>/dev/null || stat -f%z "$output_file" 2>/dev/null || echo "0")
    
    if [[ "$file_size" -lt 1000 ]]; then
        fail "TTS output too small: $file_size bytes (expected >1KB)"
        rm -f "$output_file"
        return 1
    fi
    
    # Check it's a valid WAV file
    if ! file "$output_file" | grep -qiE "audio|wav|riff"; then
        warn "TTS output may not be valid WAV: $(file "$output_file")"
        pass "TTS generates audio file ($file_size bytes)"
    else
        pass "TTS generates valid WAV audio ($file_size bytes)"
    fi
    
    rm -f "$output_file"
}

# Test 3: Embeddings produce vectors
test_embeddings_functional() {
    echo ""
    echo "> Testing Embeddings Vector Generation"
    
    local test_text="This is a test sentence for embeddings."
    local payload="{\"inputs\": \"$test_text\"}"
    
    local response
    response=$(curl -s --max-time 30 \
        -X POST "$EMBEDDING_URL/embed" \
        -H "Content-Type: application/json" \
        -d "$payload" 2>/dev/null || echo "")
    
    if [[ -z "$response" ]]; then
        # Try alternate endpoint
        response=$(curl -s --max-time 30 \
            -X POST "$EMBEDDING_URL/" \
            -H "Content-Type: application/json" \
            -d "$payload" 2>/dev/null || echo "")
    fi
    
    if [[ -z "$response" ]]; then
        fail "Embeddings returned no response"
        return 1
    fi
    
    # Check if response contains array of numbers
    if echo "$response" | grep -qE '\[\s*-?[0-9]+\.[0-9]+'; then
        local vector_len=0
        if ! vector_len=$(echo "$response" | grep -oE '-?[0-9]+\.[0-9]+' | wc -l); then
            vector_len=0
        fi
        pass "Embeddings generates vectors ($vector_len dimensions)"
    else
        fail "Embeddings did not return valid vectors"
        return 1
    fi
}

# Test 4: Whisper transcribes audio (if test audio available)
test_whisper_functional() {
    echo ""
    echo "> Testing Whisper Transcription"
    
    # Create a simple test audio file or use existing
    local test_audio="/tmp/test_audio.wav"
    
    # Try to generate test audio with TTS first
    local tts_payload='{"model": "kokoro", "input": "Hello world", "voice": "af_bella", "response_format": "wav"}'
    
    if ! curl -s --max-time 15 \
        -X POST "$TTS_URL/v1/audio/speech" \
        -H "Content-Type: application/json" \
        -d "$tts_payload" \
        -o "$test_audio" 2>/dev/null; then
        warn "Could not generate test audio for Whisper"
        warn "Skipping Whisper functional test (TTS dependency)"
        return 0
    fi
    
    if [[ ! -f "$test_audio" ]] || [[ "$(stat -c%s "$test_audio" 2>/dev/null || stat -f%z "$test_audio" 2>/dev/null || echo 0)" -lt 1000 ]]; then
        warn "Test audio generation failed"
        return 0
    fi
    
    # Transcribe with Whisper
    local response
    response=$(curl -s --max-time 30 \
        -X POST "$WHISPER_URL/v1/audio/transcriptions" \
        -H "Content-Type: multipart/form-data" \
        -F "file=@$test_audio" \
        -F "model=whisper-1" 2>/dev/null || echo "")
    
    rm -f "$test_audio"
    
    if [[ -z "$response" ]]; then
        fail "Whisper returned no response"
        return 1
    fi
    
    local transcription=""
    if ! transcription=$(echo "$response" | grep -oE '"text":[[:space:]]*"[^"]+"' | head -1 | cut -d'"' -f4); then
        transcription=""
    fi
    
    if [[ -z "$transcription" ]]; then
        fail "Whisper returned empty transcription"
        return 1
    fi
    
    if echo "$transcription" | grep -qiE "hello|world"; then
        pass "Whisper transcribes correctly: '$transcription'"
    else
        warn "Whisper transcribed: '$transcription'"
        pass "Whisper generates transcription"
    fi
}

# Main
echo "========================================"
echo "  ODS - FUNCTIONAL TESTS"
echo "  Tests actual functionality, not ports"
echo "========================================"

# Each test returns 1 on failure via its internal `fail` call; we must not
# let `set -e` short-circuit the remaining tests or skip the summary. The
# TESTS_PASSED / TESTS_FAILED counters are updated inside pass/fail and
# drive the final exit code below, so suspending strict mode for exactly
# this block is the explicit expression of "errors here are accounted for."
# (CLAUDE.md forbids `|| true` / silent swallow; this bounded toggle makes
# the intent visible instead of hiding it behind a trailing `|| true`.)
set +e
test_llm_functional
test_tts_functional
test_embeddings_functional
test_whisper_functional
set -e

echo ""
echo "========================================"
echo "  Results: $TESTS_PASSED passed, $TESTS_FAILED failed"
echo "========================================"

if [[ $TESTS_FAILED -eq 0 ]]; then
    echo -e "${GREEN}✓ All functional tests passed${NC}"
    exit 0
else
    echo -e "${RED}✗ Some functional tests failed${NC}"
    exit 1
fi
