import os
import tempfile
from unittest.mock import MagicMock, patch

import pytest
import requests
import requests.exceptions

from report_creator.utilities import (
    _check_html_tags_are_closed,
    _convert_filepath_to_datauri,
    _convert_imgurl_to_datauri_async,
    _convert_imgurl_to_datauri_sync,
    _ellipsis_url,
    _generate_anchor_id,
    _get_url_root,
    _gfm_markdown_to_html,
    _is_number,
    _random_color_generator,
    _random_light_color_generator,
    _strip_whitespace,
    _time_it,
)


def test_is_number():
    assert _is_number(1) is True
    assert _is_number(1.5) is True
    assert _is_number("1.5") is True
    assert _is_number("abc") is False
    assert _is_number(None) is False
    assert _is_number([]) is False


def test_ellipsis_url():
    url = "https://www.example.com/some/long/path/to/resource"
    # len(url) is 49

    # <= max_length
    assert _ellipsis_url(url, 50) == url

    # max_length < 4
    assert _ellipsis_url(url, 3) == "htt"

    # max_length < 10
    assert _ellipsis_url(url, 8) == "https..."

    # middle ellipsis
    truncated = _ellipsis_url(url, 20)
    assert len(truncated) == 20
    assert "..." in truncated
    assert truncated.startswith("https")
    assert truncated.endswith("rce")


def test_check_html_tags_are_closed_mismatched():
    # Line 132: Encountered end tag but stack top is different or empty
    html = "<div></span>"
    is_closed, tags = _check_html_tags_are_closed(html)
    assert is_closed is False
    assert tags == ["div"]

    html = "</span>"
    is_closed, tags = _check_html_tags_are_closed(html)
    assert is_closed is True  # Wait, the tag_stack will be empty, so it returns True?
    # Let's check the code:
    # if self.tag_stack and self.tag_stack[-1] == tag: self.tag_stack.pop()
    # else: logger.debug(...)
    # if not self.tag_stack: return (True, None)
    # So if it's just </span>, tag_stack is empty. It doesn't pop. tag_stack remains empty. Returns (True, None).
    # This matches the "concern about unclosed tags" comment.


def test_gfm_markdown_to_html_extended():
    # Test script blocking (Line 246-263)
    md_script = "<script>alert('xss')</script>"
    html = _gfm_markdown_to_html(md_script)
    assert "Potentially unsafe script tag was blocked" in html

    # Test mermaid (Line 286)
    md_mermaid = "```mermaid\ngraph TD;\nA-->B;\n```"
    html = _gfm_markdown_to_html(md_mermaid)
    assert 'class="mermaid include_mermaid"' in html
    assert "graph TD;" in html

    # Test emoji (Line 246-263)
    md_emoji = "Hello :smile:"
    html = _gfm_markdown_to_html(md_emoji)
    assert "😄" in html


def test_random_color_generator_branches():
    # Try different seeds to hit both luminance branches
    # Light background (luminance > 0.5)
    bg1, text1 = _random_color_generator("white")
    # Dark background (luminance <= 0.5)
    bg2, text2 = _random_color_generator("black")

    assert text1 in ["black", "white"]
    assert text2 in ["black", "white"]
    assert text1 != text2 or (text1 == text2)  # Just ensuring they run


def test_strip_whitespace_non_string():
    # Line 367: return result as-is if not a string
    @_strip_whitespace
    def return_int():
        return 123

    assert return_int() == 123


def test_random_light_color_generator():
    # Line 567-574
    bg, text = _random_light_color_generator("test")
    assert bg.startswith("#")
    assert text == "black"
    # Determinism
    bg2, text2 = _random_light_color_generator("test")
    assert bg == bg2


def test_random_color_generator():
    # Line 598, 605-608, 622-631
    bg, text = _random_color_generator("test")
    assert bg.startswith("#")
    assert text in ["black", "white"]
    # Determinism
    bg2, text2 = _random_color_generator("test")
    assert bg == bg2


def test_get_url_root():
    # Line 641-663
    assert _get_url_root("https://example.com/path") == "https://example.com"
    assert _get_url_root("http://localhost:8080/a/b") == "http://localhost:8080"
    # Malformed URL
    with patch("report_creator.utilities.urlparse", side_effect=Exception("parse error")):
        assert _get_url_root("invalid") == ""


def test_convert_filepath_to_datauri():
    # Line 671-714
    with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
        f.write(b"hello world")
        temp_path = f.name

    try:
        uri = _convert_filepath_to_datauri(temp_path)
        assert uri.startswith("data:text/plain;base64,")
        assert "aGVsbG8gd29ybGQ=" in uri  # base64 for "hello world"

        # Mock OSError
        with patch("builtins.open", side_effect=OSError("read error")):
            with pytest.raises(ValueError, match="IO error"):
                _convert_filepath_to_datauri(temp_path)
    finally:
        if os.path.exists(temp_path):
            os.unlink(temp_path)

    # File not found
    with pytest.raises(FileNotFoundError):
        _convert_filepath_to_datauri("non_existent_file.xyz")


def test_convert_imgurl_to_datauri_sync():
    # Line 724
    url = "https://example.com/image.png"
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.content = b"fake-image-data"
    mock_response.headers = {"Content-Type": "image/png"}

    with patch("requests.get", return_value=mock_response):
        uri = _convert_imgurl_to_datauri_sync(url)
        assert uri.startswith("data:image/png;base64,")

    # Test exceptions
    mock_response.raise_for_status.side_effect = Exception("HTTP error")
    with patch("requests.get", return_value=mock_response):
        with pytest.raises(ValueError, match="unexpected error"):  # Generic exception catch
            _convert_imgurl_to_datauri_sync(url)


def test_convert_imgurl_to_datauri_async():
    # Line 738
    url = "https://example.com/async-image.png"
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.content = b"async-data"
    mock_response.headers = {"Content-Type": "image/png"}

    with patch("requests.get", return_value=mock_response):
        future = _convert_imgurl_to_datauri_async(url)
        uri = future.result()
        assert uri.startswith("data:image/png;base64,")


def test_time_it():
    @_time_it
    def slow_func():
        return "done"

    assert slow_func() == "done"


def test_generate_anchor_id():
    # Just in case it wasn't fully covered
    id1 = _generate_anchor_id("test")
    id2 = _generate_anchor_id("test")
    assert id1 == id2
    assert isinstance(id1, str)
    assert len(id1) == 32  # hex representation of UUID


def test_convert_filepath_to_datauri_no_mime():
    with tempfile.NamedTemporaryFile(suffix="", delete=False) as f:
        f.write(b"data")
        temp_path = f.name
    try:
        # Mock mimetypes.guess_type to return None
        with patch("mimetypes.guess_type", return_value=(None, None)):
            uri = _convert_filepath_to_datauri(temp_path)
            assert "application/octet-stream" in uri
    finally:
        os.unlink(temp_path)


def test_convert_imgurl_to_datauri_sync_exceptions():
    url = "https://example.com/image.png"

    # Timeout
    with patch("requests.get", side_effect=requests.exceptions.Timeout("timeout")):
        with pytest.raises(ValueError, match="Timeout"):
            _convert_imgurl_to_datauri_sync(url)

    # RequestException
    with patch("requests.get", side_effect=requests.exceptions.RequestException("error")):
        with pytest.raises(ValueError, match="request failure"):
            _convert_imgurl_to_datauri_sync(url)

    # Missing MIME type
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.headers = {}  # No content type
    with patch("requests.get", return_value=mock_response):
        with patch("mimetypes.guess_type", return_value=(None, None)):
            with pytest.raises(ValueError, match="MIME type"):
                _convert_imgurl_to_datauri_sync(url)
