import time
import pytest
from tksessentials import database, utils
from tksessentials.database import KSQLNotReadyError
from unittest.mock import patch
import httpx
import asyncio
from kafka.admin import KafkaAdminClient, NewTopic
from kafka import KafkaConsumer
from kafka.errors import TopicAlreadyExistsError

@pytest.fixture(scope="module")
def setup():
    print("\n")
    print("REMINDER. All tests require a Kafka broker and a ksqlDB instance to be available.")
    print(f"\tENV: {utils.get_environment()}")
    print(f"\tkafka: {database.get_kafka_cluster_brokers()}")
    print(f"\tksqldb: {database.get_ksqldb_url()}")

def test_get_kafka_cluster_brokers_dev():
    # Mock utils.get_environment() to return 'DEV'
    with patch('tksessentials.utils.get_environment', return_value='DEV'):
        brokers = database.get_kafka_cluster_brokers()
        # Check if the returned brokers are as expected
        assert brokers == ['localhost:9092']

def test_get_kafka_cluster_brokers_non_dev():
    # Mock utils.get_environment() to return 'PROD'
    with patch('tksessentials.utils.get_environment', return_value='PROD'):
        # Mock os.getenv to return a specific broker string
        with patch('os.getenv', return_value='broker1.svc.cluster.local:9092,broker2.svc.cluster.local:9093'):
            brokers = database.get_kafka_cluster_brokers()
            # Check if the returned brokers contain the expected substring
            assert 'broker1.svc.cluster.local:9092' in brokers
            assert brokers == ['broker1.svc.cluster.local:9092', 'broker2.svc.cluster.local:9093']

def test_table_or_view_exists():
    max_retries = 5
    delay = 10  # seconds

    for attempt in range(max_retries):
        try:
            assert not database.table_or_view_exists("NON_EXISTENT_TABLE")
            break
        except (
            httpx.ConnectError,
            KSQLNotReadyError,
            httpx.RemoteProtocolError,
            httpx.ReadError,
        ) as e:
            if attempt < max_retries - 1:
                print(
                    f"Attempt {attempt + 1}/{max_retries} failed with error: {e}. Retrying in {delay} seconds..."
                )
                time.sleep(delay)
            else:
                raise
        except Exception as e:
            print(f"Test failed with an unexpected error: {e}. {e.args}")
            raise

@pytest.mark.asyncio
async def test_create_table():
    TABLE_NAME = "TEST_TABLE"
    sql_statement = f"""
    CREATE TABLE {TABLE_NAME}(
        event_timestamp BIGINT PRIMARY KEY,
        detail STRING,
        data STRING
    ) WITH (
        KAFKA_TOPIC='test_topic',
        VALUE_FORMAT='JSON',
        PARTITIONS=1
    );
    """

    max_retries = 5
    delay = 10  # seconds

    for attempt in range(max_retries):
        try:
            await database.create_table(sql_statement, TABLE_NAME)
            assert database.table_or_view_exists(TABLE_NAME)
            break
        except (
            httpx.ConnectError,
            KSQLNotReadyError,
            httpx.RemoteProtocolError,
            httpx.ReadError,
        ) as e:
            if attempt < max_retries - 1:
                print(
                    f"Attempt {attempt + 1}/{max_retries} failed with error: {e}. Retrying in {delay} seconds..."
                )
                time.sleep(delay)
            else:
                raise
        except Exception as e:
            print(f"Test failed with an unexpected error: {e}")
            raise

@pytest.mark.asyncio
async def test_prepare_sql_statement():
    sql_statement = """
    CREATE TABLE TEST_TABLE_PARTITION(
        event_timestamp BIGINT PRIMARY KEY,
        detail STRING,
        data STRING,
        signal_data STRUCT<
            provider_signal_id STRING,
            provider_trade_id STRING,
            provider_id STRING,
            strategy_id STRING,
            is_hot_signal BOOLEAN,
            market STRING,
            data_source STRING,
            direction STRING,
            side STRING,
            order_type STRING,
            price DOUBLE,
            tp DOUBLE,
            sl DOUBLE,
            position_size_in_percentage INT,
            date_of_creation BIGINT
        >,
        ip STRING
    ) WITH (
        KAFKA_TOPIC='test_topic_partition',
        VALUE_FORMAT='JSON',
        TIMESTAMP='event_timestamp',
        PARTITIONS=3
    );
    """

    with patch('tksessentials.database.topic_exists', return_value=True):
        prepared_statement = await database.prepare_sql_statement(sql_statement)
        assert "PARTITIONS=3" not in prepared_statement

    with patch('tksessentials.database.topic_exists', return_value=False):
        prepared_statement = await database.prepare_sql_statement(sql_statement)
        assert "PARTITIONS=3" in prepared_statement

@pytest.mark.asyncio
async def test_prepare_sql_statement_missing():
    """This test checks if the preparation works as well, wenn there is no PARTITION?"""
    sql_statement = """
    CREATE TABLE TEST_TABLE_PARTITION(
        event_timestamp BIGINT PRIMARY KEY,
        detail STRING,
        data STRING,
        signal_data STRUCT<
            provider_signal_id STRING,
            provider_trade_id STRING,
            provider_id STRING,
            strategy_id STRING,
            is_hot_signal BOOLEAN,
            market STRING,
            data_source STRING,
            direction STRING,
            side STRING,
            order_type STRING,
            price DOUBLE,
            tp DOUBLE,
            sl DOUBLE,
            position_size_in_percentage INT,
            date_of_creation BIGINT
        >,
        ip STRING
    ) WITH (
        KAFKA_TOPIC='test_topic_partition',
        VALUE_FORMAT='JSON',
        TIMESTAMP='event_timestamp'
    );
    """

    with patch('tksessentials.database.topic_exists', return_value=True):
        prepared_statement = await database.prepare_sql_statement(sql_statement)
        assert "PARTITIONS=3" not in prepared_statement

    with patch('tksessentials.database.topic_exists', return_value=False):
        prepared_statement = await database.prepare_sql_statement(sql_statement)
        assert "PARTITIONS=3" in prepared_statement

def test_clean_sql_statement():
    sql_statement = """
    CREATE TABLE fa_signal_processing_trading_signal_received(
        event_timestamp BIGINT PRIMARY KEY,
        detail STRING,
        data STRING,
        signal_data STRUCT<
            provider_signal_id STRING,
            provider_trade_id STRING,
            provider_id STRING,
            strategy_id STRING,
            is_hot_signal BOOLEAN,
            market STRING,
            data_source STRING,
            direction STRING,
            side STRING,
            order_type STRING,
            price DOUBLE,
            tp DOUBLE,
            sl DOUBLE,
            position_size_in_percentage INT,
            date_of_creation BIGINT
        >,
        ip STRING
    ) WITH (
        KAFKA_TOPIC='fa_signal_processing.trading_signal_received',
        VALUE_FORMAT='JSON',
        TIMESTAMP='event_timestamp',
        PARTITIONS=3
    );
    """

    expected_cleaned_sql = "CREATE TABLE fa_signal_processing_trading_signal_received( event_timestamp BIGINT PRIMARY KEY, detail STRING, data STRING, signal_data STRUCT< provider_signal_id STRING, provider_trade_id STRING, provider_id STRING, strategy_id STRING, is_hot_signal BOOLEAN, market STRING, data_source STRING, direction STRING, side STRING, order_type STRING, price DOUBLE, tp DOUBLE, sl DOUBLE, position_size_in_percentage INT, date_of_creation BIGINT >, ip STRING ) WITH ( KAFKA_TOPIC='fa_signal_processing.trading_signal_received', VALUE_FORMAT='JSON', TIMESTAMP='event_timestamp', PARTITIONS=3 );"

    cleaned_sql = database.clean_sql_statement(sql_statement)
    assert cleaned_sql == expected_cleaned_sql

def test_stream_exists():
    max_retries = 5
    delay = 10  # seconds

    for attempt in range(max_retries):
        try:
            assert not database.stream_exists("NON_EXISTENT_STREAM")
            break
        except (
            httpx.ConnectError,
            KSQLNotReadyError,
            httpx.RemoteProtocolError,
            httpx.ReadError,
        ) as e:
            if attempt < max_retries - 1:
                print(
                    f"Attempt {attempt + 1}/{max_retries} failed with error: {e}. Retrying in {delay} seconds..."
                )
                time.sleep(delay)
            else:
                raise
        except Exception as e:
            print(f"Test failed with an unexpected error: {e}. {e.args}")
            raise

@pytest.mark.asyncio
async def test_create_stream():
    STREAM_NAME = "TEST_STREAM"
    sql_statement = f"""
    CREATE STREAM {STREAM_NAME}(
        event_timestamp BIGINT,
        detail STRING,
        data STRING
    ) WITH (
        KAFKA_TOPIC='test_topic',
        VALUE_FORMAT='JSON',
        PARTITIONS=1
    );
    """

    max_retries = 5
    delay = 10  # seconds

    for attempt in range(max_retries):
        try:
            await database.create_stream(sql_statement, STREAM_NAME)
            assert database.stream_exists(STREAM_NAME)
            break
        except (
            httpx.ConnectError,
            KSQLNotReadyError,
            httpx.RemoteProtocolError,
            httpx.ReadError,
        ) as e:
            if attempt < max_retries - 1:
                print(
                    f"Attempt {attempt + 1}/{max_retries} failed with error: {e}. Retrying in {delay} seconds..."
                )
                time.sleep(delay)
            else:
                raise
        except Exception as e:
            print(f"Test failed with an unexpected error: {e}")
            raise