"""Unit tests for DuckDBMetricTool (refactored)."""

import pytest
from unittest.mock import Mock, patch
import sys
import os
import pandas as pd

# Add parent directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from langchain_iceberg.tools.duckdb_metric_tool_refactored import DuckDBMetricTool
from langchain_iceberg.exceptions import IcebergInvalidQueryError


class TestDuckDBMetricTool:
    """Test suite for DuckDBMetricTool class."""

    def test_init_creates_dynamic_tool_name(self, mock_catalog, catalog_config, 
                                            sample_metric_config, semantic_config):
        """Test that tool name is dynamically generated from metric name."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb'):
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=sample_metric_config,
                semantic_config=semantic_config,
            )
            
            assert tool.name == "get_total_revenue"
            assert "total revenue" in tool.description.lower()

    def test_build_description(self, mock_catalog, catalog_config, 
                               sample_metric_config, semantic_config):
        """Test description building from metric config."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb'):
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=sample_metric_config,
                semantic_config=semantic_config,
            )
            
            desc = tool.description
            assert "Total revenue from completed orders" in desc
            assert "date_range" in desc
            assert "Q4_2024" in desc  # Example in description

    def test_run_simple_formula(self, mock_catalog, catalog_config, 
                                sample_metric_config, semantic_config, mock_table):
        """Test running a simple SQL formula metric."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb') as mock_duckdb:
            # Setup mocks
            mock_conn = Mock()
            mock_result = Mock()
            mock_df = pd.DataFrame({"total_revenue": [125000.50]})
            mock_result.fetchdf.return_value = mock_df
            mock_conn.execute.return_value = mock_result
            mock_duckdb.connect.return_value = mock_conn
            
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=sample_metric_config,
                semantic_config=semantic_config,
            )
            
            # Mock table loading
            mock_catalog.load_table.return_value = mock_table
            
            result = tool._run()
            
            assert result is not None
            assert isinstance(result, str)
            mock_conn.execute.assert_called()

    def test_run_with_date_range(self, mock_catalog, catalog_config, 
                                 sample_metric_config, semantic_config, mock_table):
        """Test running metric with date range filter."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb') as mock_duckdb:
            # Setup mocks
            mock_conn = Mock()
            mock_result = Mock()
            mock_df = pd.DataFrame({"total_revenue": [75000.25]})
            mock_result.fetchdf.return_value = mock_df
            mock_conn.execute.return_value = mock_result
            mock_duckdb.connect.return_value = mock_conn
            
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=sample_metric_config,
                semantic_config=semantic_config,
            )
            
            # Mock table loading
            mock_catalog.load_table.return_value = mock_table
            
            result = tool._run(date_range="Q4_2024")
            
            assert result is not None
            assert "Q4_2024" in result
            
            # Check that SQL was modified with date filter
            executed_sql = str(mock_conn.execute.call_args)
            assert "order_date" in executed_sql or "WHERE" in executed_sql

    def test_run_complex_join_query(self, mock_catalog, catalog_config, 
                                    complex_metric_config, semantic_config, mock_table):
        """Test running metric with JOINs."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb') as mock_duckdb:
            # Setup mocks
            mock_conn = Mock()
            mock_result = Mock()
            mock_df = pd.DataFrame({
                "customer_segment": ["Premium", "Standard", "Basic"],
                "total_revenue": [150000, 80000, 35000]
            })
            mock_result.fetchdf.return_value = mock_df
            mock_conn.execute.return_value = mock_result
            mock_duckdb.connect.return_value = mock_conn
            
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=complex_metric_config,
                semantic_config=semantic_config,
            )
            
            # Mock table loading
            mock_catalog.load_table.return_value = mock_table
            
            result = tool._run()
            
            assert result is not None
            # Should register both tables
            assert "sales.orders" in tool._registered_tables
            assert "sales.customers" in tool._registered_tables

    def test_inject_date_filter_no_where(self, mock_catalog, catalog_config, 
                                         sample_metric_config, semantic_config):
        """Test date filter injection when SQL has no WHERE clause."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb'):
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=sample_metric_config,
                semantic_config=semantic_config,
            )
            
            sql = "SELECT SUM(amount) FROM sales.orders GROUP BY segment"
            filtered_sql = tool._inject_date_filter(sql, "Q1_2024")
            
            assert "WHERE" in filtered_sql
            assert "order_date" in filtered_sql
            assert "2024-01-01" in filtered_sql

    def test_inject_date_filter_existing_where(self, mock_catalog, catalog_config, 
                                               sample_metric_config, semantic_config):
        """Test date filter injection when SQL already has WHERE clause."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb'):
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=sample_metric_config,
                semantic_config=semantic_config,
            )
            
            sql = "SELECT SUM(amount) FROM sales.orders WHERE status = 'completed'"
            filtered_sql = tool._inject_date_filter(sql, "Q1_2024")
            
            # Should add to existing WHERE with AND
            assert filtered_sql.count("WHERE") == 1
            assert "AND" in filtered_sql or "and" in filtered_sql
            assert "order_date" in filtered_sql

    def test_no_formula_error(self, mock_catalog, catalog_config, semantic_config):
        """Test error when metric has no formula."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb'):
            # Metric with no formula
            bad_config = {
                "name": "bad_metric",
                "description": "Metric with no formula",
                "type": "calculated",
                # No formula!
            }
            
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=bad_config,
                semantic_config=semantic_config,
            )
            
            with pytest.raises(IcebergInvalidQueryError, match="has no formula"):
                tool._run()

    def test_date_filter_no_date_column(self, mock_catalog, catalog_config, 
                                        semantic_config):
        """Test warning when date column cannot be found."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb'):
            # Metric with no date_column specified
            config_no_date = {
                "name": "test_metric",
                "formula": "SELECT COUNT(*) FROM sales.orders",
                # No date_column!
            }
            
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=config_no_date,
                semantic_config=semantic_config,
            )
            
            sql = "SELECT COUNT(*) FROM sales.orders"
            
            # Should return unmodified SQL with warning
            with patch('warnings.warn') as mock_warn:
                filtered_sql = tool._inject_date_filter(sql, "Q1_2024")
                mock_warn.assert_called()
                assert filtered_sql == sql  # Unchanged

    def test_date_filter_guesses_column(self, mock_catalog, catalog_config, 
                                        semantic_config):
        """Test date filter guesses common date column names."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb'):
            # Metric with no date_column, but SQL has common date column
            config_no_date = {
                "name": "test_metric",
                "formula": "SELECT COUNT(*), created_at FROM sales.orders GROUP BY created_at",
            }
            
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=config_no_date,
                semantic_config=semantic_config,
            )
            
            sql = config_no_date["formula"]
            filtered_sql = tool._inject_date_filter(sql, "Q1_2024")
            
            # Should find and use created_at
            assert "created_at" in filtered_sql
            assert "WHERE" in filtered_sql or "where" in filtered_sql


class TestDuckDBMetricToolEdgeCases:
    """Test edge cases and error handling."""

    def test_metric_with_tags(self, mock_catalog, catalog_config, semantic_config):
        """Test metric with tags in description."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb'):
            config_with_tags = {
                "name": "tagged_metric",
                "description": "Metric with tags",
                "formula": "SELECT SUM(amount) FROM sales.orders",
                "tags": ["revenue", "sales", "kpi"],
            }
            
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=config_with_tags,
                semantic_config=semantic_config,
            )
            
            assert "revenue" in tool.description
            assert "sales" in tool.description
            assert "kpi" in tool.description

    def test_format_results_no_metadata(self, mock_catalog, catalog_config, 
                                        sample_metric_config, semantic_config, mock_table):
        """Test that metric results don't include execution metadata."""
        with patch('langchain_iceberg.tools.duckdb_base.duckdb') as mock_duckdb:
            mock_conn = Mock()
            mock_result = Mock()
            mock_df = pd.DataFrame({"total": [1000]})
            mock_result.fetchdf.return_value = mock_df
            mock_conn.execute.return_value = mock_result
            mock_duckdb.connect.return_value = mock_conn
            
            tool = DuckDBMetricTool(
                catalog=mock_catalog,
                catalog_config=catalog_config,
                metric_config=sample_metric_config,
                semantic_config=semantic_config,
            )
            
            mock_catalog.load_table.return_value = mock_table
            
            result = tool._run()
            
            # Should NOT have execution time (unlike ad-hoc SQL tool)
            assert "Executed in" not in result
            assert "ms" not in result
