"""Tests for UGC features: source_lang, background mode, two-tier lookup, TranslationResult."""

import hashlib
import time
import threading
import pytest
from unittest.mock import patch, MagicMock

import transduck
from transduck import ait, ait_plural, set_language
from transduck.config import TransduckConfig
from transduck.result import TranslationResult


def _hash(text: str) -> str:
    return hashlib.sha256(text.encode()).hexdigest()


@pytest.fixture
def mock_config(tmp_path):
    return TransduckConfig(
        project_name="test",
        project_context="A test site",
        source_lang="EN",
        target_langs=["DE", "ES"],
        storage_path=tmp_path / "test.db",
        provider="openai",
        api_key_env="OPENAI_API_KEY",
        token_env="",
        backend_model="gpt-4.1-mini",
        backend_timeout=10,
        backend_max_retries=2,
        shared_url=None,
        read_only=False,
    )


@pytest.fixture(autouse=True)
def reset_state():
    transduck._state = transduck._State()
    yield
    transduck._wait_for_background(timeout=2.0)
    transduck._state = transduck._State()


@pytest.fixture
def initialized(mock_config, monkeypatch):
    monkeypatch.setenv("OPENAI_API_KEY", "test-key")
    transduck.initialize(mock_config)
    set_language("DE")


# --- TranslationResult return type ---

def test_ait_returns_translation_result(initialized):
    transduck._state.store.insert(
        source_text="Hello", source_lang="EN", target_lang="DE",
        project_context_hash=_hash("A test site"), string_context_hash=_hash(""),
        translated_text="Hallo", model="gpt-4.1-mini", string_context="",
    )
    result = ait("Hello")
    assert isinstance(result, TranslationResult)
    assert result == "Hallo"
    assert result.pending is False
    assert result.source_lang == "EN"
    assert result.lang == "DE"


def test_ait_same_language_returns_result(initialized):
    set_language("EN")
    result = ait("Hello")
    assert isinstance(result, TranslationResult)
    assert result == "Hello"
    assert result.pending is False
    assert result.source_lang == "EN"
    assert result.lang == "EN"


# --- source_lang parameter ---

@patch("transduck.backend.translate")
def test_ait_source_lang_override(mock_translate, initialized):
    mock_translate.return_value = "Hello"
    result = ait("Hola", source_lang="ES")
    assert result == "Hello"
    call_kwargs = mock_translate.call_args[1]
    assert call_kwargs["source_lang"] == "ES"


def test_ait_source_lang_same_as_target(initialized):
    """If source_lang matches target_lang, return source text directly."""
    set_language("ES")
    result = ait("Hola mundo", source_lang="ES")
    assert result == "Hola mundo"
    assert result.pending is False


@patch("transduck.backend.translate")
def test_ait_source_lang_normalizes(mock_translate, initialized):
    """source_lang is uppercased."""
    mock_translate.return_value = "Hallo"
    ait("Hello", source_lang="en")
    call_kwargs = mock_translate.call_args[1]
    assert call_kwargs["source_lang"] == "EN"


# --- background mode ---

@patch("transduck.backend.translate")
def test_ait_background_returns_pending(mock_translate, initialized):
    event = threading.Event()
    def slow_translate(**kwargs):
        event.wait(timeout=5)
        return "Hallo"
    mock_translate.side_effect = slow_translate

    result = ait("Hello", background=True)
    assert isinstance(result, TranslationResult)
    assert result.pending is True
    assert result == "Hello"  # returns source text while pending
    assert result.source_lang == "EN"
    assert result.lang == "DE"
    event.set()  # unblock the background thread


@patch("transduck.backend.translate")
def test_ait_background_deduplicates(mock_translate, initialized):
    """Multiple background calls for same string don't spawn multiple threads."""
    event = threading.Event()
    call_count = 0
    def slow_translate(**kwargs):
        nonlocal call_count
        call_count += 1
        event.wait(timeout=5)
        return "Hallo"
    mock_translate.side_effect = slow_translate

    ait("Hello", background=True)
    ait("Hello", background=True)
    ait("Hello", background=True)
    event.set()
    time.sleep(0.1)  # let bg thread finish
    assert call_count == 1


@patch("transduck.backend.translate")
def test_ait_background_stores_result(mock_translate, initialized):
    """Background translation stores result for next call."""
    mock_translate.return_value = "Hallo"

    result1 = ait("Hello", background=True)
    assert result1.pending is True

    time.sleep(0.2)  # let bg thread finish

    result2 = ait("Hello")
    assert result2 == "Hallo"
    assert result2.pending is False


# --- two-tier lookup ---

def test_ait_shared_store_fallback(tmp_path, monkeypatch):
    """When local cache misses but shared store hits, use shared result."""
    monkeypatch.setenv("OPENAI_API_KEY", "test-key")

    mock_shared = MagicMock()
    mock_shared.lookup.return_value = "Hallo"

    cfg = TransduckConfig(
        project_name="test", project_context="A test site",
        source_lang="EN", target_langs=["DE"],
        storage_path=tmp_path / "test.db",
        provider="openai", api_key_env="OPENAI_API_KEY", token_env="",
        backend_model="gpt-4.1-mini", backend_timeout=10, backend_max_retries=2,
        shared_url=None, read_only=False,
    )
    transduck.initialize(cfg)
    set_language("DE")
    transduck._state.shared_store = mock_shared

    result = ait("Hello")
    assert result == "Hallo"
    assert result.pending is False
    mock_shared.lookup.assert_called_once()
    # Also propagated to local store
    local = transduck._state.store.lookup(
        source_text="Hello", source_lang="EN", target_lang="DE",
        project_context_hash=_hash("A test site"), string_context_hash=_hash(""),
    )
    assert local == "Hallo"


@patch("transduck.backend.translate")
def test_ait_writes_to_both_stores(mock_translate, tmp_path, monkeypatch):
    """New translations are written to both local and shared stores."""
    monkeypatch.setenv("OPENAI_API_KEY", "test-key")
    mock_translate.return_value = "Hallo"

    mock_shared = MagicMock()
    mock_shared.lookup.return_value = None

    cfg = TransduckConfig(
        project_name="test", project_context="A test site",
        source_lang="EN", target_langs=["DE"],
        storage_path=tmp_path / "test.db",
        provider="openai", api_key_env="OPENAI_API_KEY", token_env="",
        backend_model="gpt-4.1-mini", backend_timeout=10, backend_max_retries=2,
        shared_url=None, read_only=False,
    )
    transduck.initialize(cfg)
    set_language("DE")
    transduck._state.shared_store = mock_shared

    ait("Hello")
    mock_shared.insert.assert_called_once()
    call_kwargs = mock_shared.insert.call_args[1]
    assert call_kwargs["translated_text"] == "Hallo"


def test_ait_shared_store_error_does_not_break(tmp_path, monkeypatch):
    """If shared store errors, local store still works."""
    monkeypatch.setenv("OPENAI_API_KEY", "test-key")

    mock_shared = MagicMock()
    mock_shared.lookup.side_effect = Exception("DB down")

    cfg = TransduckConfig(
        project_name="test", project_context="A test site",
        source_lang="EN", target_langs=["DE"],
        storage_path=tmp_path / "test.db",
        provider="openai", api_key_env="OPENAI_API_KEY", token_env="",
        backend_model="gpt-4.1-mini", backend_timeout=10, backend_max_retries=2,
        shared_url=None, read_only=False,
    )
    transduck.initialize(cfg)
    set_language("DE")
    transduck._state.shared_store = mock_shared

    # Pre-insert in local
    transduck._state.store.insert(
        source_text="Hello", source_lang="EN", target_lang="DE",
        project_context_hash=_hash("A test site"), string_context_hash=_hash(""),
        translated_text="Hallo", model="gpt-4.1-mini", string_context="",
    )
    result = ait("Hello")
    assert result == "Hallo"


# --- ait_plural with new features ---

def test_ait_plural_returns_translation_result(initialized):
    set_language("EN")
    result = ait_plural("{count} item", "{count} items", count=1)
    assert isinstance(result, TranslationResult)
    assert result.pending is False


@patch("transduck.backend.translate_plural")
def test_ait_plural_source_lang_override(mock_tp, initialized):
    mock_tp.return_value = {"one": "{count} item", "other": "{count} items"}
    result = ait_plural("{count} elemento", "{count} elementos", count=5, source_lang="ES")
    call_kwargs = mock_tp.call_args[1]
    assert call_kwargs["source_lang"] == "ES"


@patch("transduck.backend.translate_plural")
def test_ait_plural_background_returns_pending(mock_tp, initialized):
    event = threading.Event()
    def slow_translate(**kwargs):
        event.wait(timeout=5)
        return {"one": "{count} Nachricht", "other": "{count} Nachrichten"}
    mock_tp.side_effect = slow_translate

    result = ait_plural("{count} message", "{count} messages", count=5, background=True)
    assert isinstance(result, TranslationResult)
    assert result.pending is True
    event.set()
