from __future__ import annotations

import logging
import os
import io
import tempfile
import time
from pathlib import Path

import app.utils.logging as app_logging
from app.utils.logging import _LogWriter, get_call_logger


class _FailingHandler(logging.Handler):
    def emit(self, record: logging.LogRecord) -> None:
        raise OSError("disk unavailable")


def test_session_console_formatter_appends_context_without_duplicating() -> None:
    stream = io.StringIO()
    handler = logging.StreamHandler(stream)
    handler.setFormatter(app_logging._SessionConsoleFormatter("%(message)s"))
    logger = logging.getLogger(f"test.session.console.{time.time_ns()}")
    logger.handlers = [handler]
    logger.propagate = False
    logger.setLevel(logging.INFO)

    try:
        app_logging.set_log_session_id("session-123")
        logger.info("FLOW | step=test")
        logger.info("FLOW | step=test | session_id=session-123")
    finally:
        app_logging.set_log_session_id("")

    assert stream.getvalue().splitlines() == [
        "FLOW | step=test | session_id=session-123",
        "FLOW | step=test | session_id=session-123",
    ]


def test_session_console_formatter_keeps_structured_json_valid() -> None:
    formatter = app_logging._SessionConsoleFormatter("%(message)s")
    try:
        app_logging.set_log_session_id("session-456")
        record = logging.LogRecord(
            "test",
            logging.INFO,
            "",
            0,
            '{"tipo_evento":"envio msg","session_id":"session-456"}',
            (),
            None,
        )

        assert formatter.format(record) == (
            '{"tipo_evento":"envio msg","session_id":"session-456"}'
        )
    finally:
        app_logging.set_log_session_id("")


def test_call_file_handler_is_async_and_writes_to_disk() -> None:
    with tempfile.TemporaryDirectory() as tmp:
        saved = {key: os.environ.get(key) for key in ("LOG_DIR", "LOG_TO_FILE")}
        os.environ["LOG_DIR"] = tmp
        os.environ["LOG_TO_FILE"] = "1"
        try:
            logger = get_call_logger(
                phone_number="5500000000123",
                session_id=f"async{time.time_ns()}",
            )
            async_handlers = [
                handler
                for handler in logger.handlers
                if isinstance(handler, app_logging._AsyncFileHandler)
            ]
            assert len(async_handlers) == 1

            logger.info("LINHA_DE_TESTE_ASYNC_123")
            assert app_logging._LOG_WRITER.flush(timeout=5.0)

            files = list(Path(tmp).glob("*.txt"))
            assert len(files) == 1
            assert "LINHA_DE_TESTE_ASYNC_123" in files[0].read_text(encoding="utf-8")
        finally:
            for key, value in saved.items():
                if value is None:
                    os.environ.pop(key, None)
                else:
                    os.environ[key] = value


def test_queue_full_is_counted_and_flush_honors_timeout() -> None:
    writer = _LogWriter(max_queue=1, warning_interval_s=3_600)
    writer.start = lambda: True  # type: ignore[method-assign]
    writer._last_drop_warning_at = time.monotonic()
    handler = logging.NullHandler()
    record = logging.LogRecord("test", logging.INFO, "", 0, "message", (), None)

    assert writer.submit(handler, record) is True
    assert writer.submit(handler, record) is False

    started = time.monotonic()
    assert writer.flush(timeout=0.01) is False
    assert time.monotonic() - started < 0.5
    assert writer.dropped == 1


def test_handler_error_is_counted_and_shutdown_stays_healthy() -> None:
    writer = _LogWriter(max_queue=10, warning_interval_s=3_600)
    writer._last_error_warning_at = time.monotonic()
    record = logging.LogRecord("test", logging.INFO, "", 0, "message", (), None)

    assert writer.submit(_FailingHandler(), record) is True
    assert writer.flush(timeout=2.0) is True
    assert writer.write_errors == 1
    assert writer.shutdown(timeout=2.0) is True
