#!/usr/bin/env python3

import time
import arrow
from flask import url_for
from .util import live_server_setup, wait_for_all_checks
from ..jinja2_custom import render


# def test_setup(client, live_server, measure_memory_usage, datastore_path):
   # #  live_server_setup(live_server) # Setup on conftest per function

# If there was only a change in the whitespacing, then we shouldnt have a change detected
def test_jinja2_in_url_query(client, live_server, measure_memory_usage, datastore_path):
    

    # Add our URL to the import page
    test_url = url_for('test_return_query', _external=True)

    # because url_for() will URL-encode the var, but we dont here
    full_url = "{}?{}".format(test_url,
                              "date={% now 'Europe/Berlin', '%Y' %}.{% now 'Europe/Berlin', '%m' %}.{% now 'Europe/Berlin', '%d' %}", )
    res = client.post(
        url_for("ui.ui_views.form_quick_watch_add"),
        data={"url": full_url, "tags": "test"},
        follow_redirects=True
    )
    assert b"Watch added" in res.data
    wait_for_all_checks(client)

    # It should report nothing found (no new 'has-unread-changes' class)
    res = client.get(
        url_for("ui.ui_preview.preview_page", uuid="first"),
        follow_redirects=True
    )
    assert b'date=2' in res.data

# Test for issue #1493 - jinja2-time offset functionality
def test_jinja2_time_offset_in_url_query(client, live_server, measure_memory_usage, datastore_path):
    """Test that jinja2 time offset expressions work in watch URLs (issue #1493)."""

    # Add our URL to the import page with time offset expression
    test_url = url_for('test_return_query', _external=True)

    # Test the exact syntax from issue #1493 that was broken in jinja2-time
    # This should work now with our custom TimeExtension
    full_url = "{}?{}".format(test_url,
                              "timestamp={% now 'utc' - 'minutes=11', '%Y-%m-%d %H:%M' %}", )
    res = client.post(
        url_for("ui.ui_views.form_quick_watch_add"),
        data={"url": full_url, "tags": "test"},
        follow_redirects=True
    )
    assert b"Watch added" in res.data
    wait_for_all_checks(client)

    # Verify the URL was processed correctly (should not have errors)
    res = client.get(
        url_for("ui.ui_preview.preview_page", uuid="first"),
        follow_redirects=True
    )
    # Should have a valid timestamp in the response
    assert b'timestamp=' in res.data
    # Should not have template error
    assert b'Invalid template' not in res.data


# https://techtonics.medium.com/secure-templating-with-jinja2-understanding-ssti-and-jinja2-sandbox-environment-b956edd60456
def test_jinja2_security_url_query(client, live_server, measure_memory_usage, datastore_path):
    # Add our URL to the import page
    test_url = url_for('test_return_query', _external=True)

    full_url = test_url + "?date={{ ''.__class__.__mro__[1].__subclasses__()}}"

    res = client.post(
        url_for("ui.ui_views.form_quick_watch_add"),
        data={"url": full_url, "tags": "test"},
        follow_redirects=True
    )
    assert b"Watch added" not in res.data


def test_timezone(mocker):
    """Verify that timezone is parsed."""

    timezone = 'America/Buenos_Aires'
    currentDate = arrow.now(timezone)
    arrowNowMock = mocker.patch("changedetectionio.jinja2_custom.extensions.TimeExtension.arrow.now")
    arrowNowMock.return_value = currentDate
    finalRender = render(f"{{% now '{timezone}' %}}")

    assert finalRender == currentDate.strftime('%a, %d %b %Y %H:%M:%S')

def test_format(mocker):
    """Verify that format is parsed."""

    timezone = 'utc'
    format = '%d %b %Y %H:%M:%S'
    currentDate = arrow.now(timezone)
    arrowNowMock = mocker.patch("arrow.now")
    arrowNowMock.return_value = currentDate
    finalRender = render(f"{{% now '{timezone}', '{format}' %}}")

    assert finalRender == currentDate.strftime(format)

def test_add_time(environment):
    """Verify that added time offset can be parsed."""

    finalRender = render("{% now 'utc' + 'hours=2,seconds=30' %}")

    assert finalRender == "Thu, 10 Dec 2015 01:33:31"

def test_add_weekday(mocker):
    """Verify that added weekday offset can be parsed."""

    timezone = 'utc'
    currentDate = arrow.now(timezone)
    arrowNowMock = mocker.patch("changedetectionio.jinja2_custom.extensions.TimeExtension.arrow.now")
    arrowNowMock.return_value = currentDate
    finalRender = render(f"{{% now '{timezone}' + 'weekday=1' %}}")

    assert finalRender == currentDate.shift(weekday=1).strftime('%a, %d %b %Y %H:%M:%S')


def test_substract_time(environment):
    """Verify that substracted time offset can be parsed."""

    finalRender = render("{% now 'utc' - 'minutes=11' %}")

    assert finalRender == "Wed, 09 Dec 2015 23:22:01"


def test_offset_with_format(environment):
    """Verify that offset works together with datetime format."""

    finalRender = render(
        "{% now 'utc' - 'days=2,minutes=33,seconds=1', '%d %b %Y %H:%M:%S' %}"
    )

    assert finalRender == "07 Dec 2015 23:00:00"

def test_default_timezone_empty_string(environment):
    """Verify that empty timezone string uses the default timezone (UTC in test environment)."""

    # Empty string should use the default timezone which is 'UTC' (or from application settings)
    finalRender = render("{% now '' %}")

    # Should render with default format and UTC timezone (matches environment fixture)
    assert finalRender == "Wed, 09 Dec 2015 23:33:01"

def test_default_timezone_with_offset(environment):
    """Verify that empty timezone works with offset operations."""

    # Empty string with offset should use default timezone
    finalRender = render("{% now '' + 'hours=2', '%d %b %Y %H:%M:%S' %}")

    assert finalRender == "10 Dec 2015 01:33:01"

def test_default_timezone_subtraction(environment):
    """Verify that empty timezone works with subtraction offset."""

    finalRender = render("{% now '' - 'minutes=11' %}")

    assert finalRender == "Wed, 09 Dec 2015 23:22:01"

def test_regex_replace_basic():
    """Test basic regex_replace functionality."""

    # Simple word replacement
    finalRender = render("{{ 'hello world' | regex_replace('world', 'universe') }}")
    assert finalRender == "hello universe"

def test_regex_replace_with_groups():
    """Test regex_replace with capture groups (issue #3501 use case)."""

    # Transform HTML table data as described in the issue
    template = "{{ '<td>thing</td><td>other</td>' | regex_replace('<td>([^<]+)</td><td>([^<]+)</td>', 'ThingLabel: \\\\1\\nOtherLabel: \\\\2') }}"
    finalRender = render(template)
    assert "ThingLabel: thing" in finalRender
    assert "OtherLabel: other" in finalRender

def test_regex_replace_multiple_matches():
    """Test regex_replace replacing multiple occurrences."""

    finalRender = render("{{ 'foo bar foo baz' | regex_replace('foo', 'qux') }}")
    assert finalRender == "qux bar qux baz"

def test_regex_replace_count_parameter():
    """Test regex_replace with count parameter to limit replacements."""

    finalRender = render("{{ 'foo bar foo baz' | regex_replace('foo', 'qux', 1) }}")
    assert finalRender == "qux bar foo baz"

def test_regex_replace_empty_replacement():
    """Test regex_replace with empty replacement (removal)."""

    finalRender = render("{{ 'hello world 123' | regex_replace('[0-9]+', '') }}")
    assert finalRender == "hello world "

def test_regex_replace_no_match():
    """Test regex_replace when pattern doesn't match."""

    finalRender = render("{{ 'hello world' | regex_replace('xyz', 'abc') }}")
    assert finalRender == "hello world"

def test_regex_replace_invalid_regex():
    """Test regex_replace with invalid regex pattern returns original value."""

    # Invalid regex (unmatched parenthesis)
    finalRender = render("{{ 'hello world' | regex_replace('(invalid', 'replacement') }}")
    assert finalRender == "hello world"

def test_regex_replace_special_characters():
    """Test regex_replace with special regex characters."""

    finalRender = render("{{ 'Price: $50.00' | regex_replace('\\\\$([0-9.]+)', 'USD \\\\1') }}")
    assert finalRender == "Price: USD 50.00"

def test_regex_replace_multiline():
    """Test regex_replace on multiline text."""

    template = "{{ 'line1\\nline2\\nline3' | regex_replace('^line', 'row') }}"
    finalRender = render(template)
    # By default re.sub doesn't use MULTILINE flag, so only first line matches with ^
    assert finalRender == "row1\nline2\nline3"

def test_regex_replace_with_notification_context():
    """Test regex_replace with notification diff variable."""

    # Simulate how it would be used in notifications with diff variable
    from changedetectionio.notification_service import NotificationContextData

    context = NotificationContextData()
    context['diff'] = '<td>value1</td><td>value2</td>'

    template = "{{ diff | regex_replace('<td>([^<]+)</td>', '\\\\1 ') }}"

    from changedetectionio.jinja2_custom import create_jinja_env
    from jinja2 import BaseLoader

    jinja2_env = create_jinja_env(loader=BaseLoader)
    jinja2_env.globals.update(context)
    finalRender = jinja2_env.from_string(template).render()

    assert "value1 value2 " in finalRender

def test_regex_replace_security_large_input():
    """Test regex_replace handles large input safely."""

    # Create a large input string (over 10MB)
    large_input = "x" * (1024 * 1024 * 10 + 1000)
    template = "{{ large_input | regex_replace('x', 'y') }}"

    from changedetectionio.jinja2_custom import create_jinja_env
    from jinja2 import BaseLoader

    jinja2_env = create_jinja_env(loader=BaseLoader)
    jinja2_env.globals['large_input'] = large_input
    finalRender = jinja2_env.from_string(template).render()

    # Should be truncated to 10MB
    assert len(finalRender) == 1024 * 1024 * 10

def test_regex_replace_security_long_pattern():
    """Test regex_replace rejects very long patterns."""

    # Pattern longer than 500 chars should be rejected
    long_pattern = "a" * 501
    finalRender = render("{{ 'test' | regex_replace('" + long_pattern + "', 'replacement') }}")

    # Should return original value when pattern is too long
    assert finalRender == "test"

def test_regex_replace_security_dangerous_pattern():
    """Test regex_replace detects and rejects dangerous nested quantifiers."""

    # Patterns that could cause catastrophic backtracking
    dangerous_patterns = [
        "(a+)+",
        "(a*)+",
        "(a+)*",
        "(a*)*",
    ]

    for dangerous in dangerous_patterns:
        # Create a template with the dangerous pattern
        # Using single quotes to avoid escaping issues
        from changedetectionio.jinja2_custom import create_jinja_env
        from jinja2 import BaseLoader

        jinja2_env = create_jinja_env(loader=BaseLoader)
        jinja2_env.globals['pattern'] = dangerous
        template = "{{ 'aaaaaaaaaa' | regex_replace(pattern, 'x') }}"
        finalRender = jinja2_env.from_string(template).render()

        # Should return original value when dangerous pattern is detected
        assert finalRender == "aaaaaaaaaa"

def test_regex_replace_security_timeout_protection():
    """Test that regex_replace has timeout protection (if SIGALRM available)."""
    import signal

    # Only test on systems that support SIGALRM
    if not hasattr(signal, 'SIGALRM'):
        # Skip test on Windows and other systems without SIGALRM
        return

    # This pattern is known to cause exponential backtracking on certain inputs
    # but should be caught by our dangerous pattern detector
    # We're mainly testing that the timeout mechanism works

    from changedetectionio.jinja2_custom import regex_replace

    # Create input that could trigger slow regex
    test_input = "a" * 50 + "b"

    # This shouldn't take long due to our protections
    result = regex_replace(test_input, "a+b", "x")

    # Should complete and return a result
    assert result is not None