#!/usr/bin/env python3
"""
Unit Tests for Social Media Audit Functionality
================================================

Tests for:
- WebsiteAuditor (scripts/social_media_audit.py)
- GooglePlacesSearcher (scripts/social_media_audit.py)
- BraveSearcher (scripts/social_media_audit.py)
- SocialMediaAuditor (scripts/social_media_audit.py)

Run tests:
    cd tests
    python -m pytest test_social_media_audit.py -v

Author: Claude Code
Date: 2026-01-08
"""

import json
import sys
import unittest
from datetime import datetime, date, timedelta
from pathlib import Path
from unittest.mock import Mock, MagicMock, patch, PropertyMock

# Add scripts directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent / 'scripts'))

# Import modules to test
from social_media_audit import (
    WebsiteAuditor,
    GooglePlacesSearcher,
    BraveSearcher,
    SocialMediaAuditor,
    HOSTING_PROVIDERS,
    SOCIAL_MEDIA_PATTERNS,
    SOCIAL_MEDIA_EXCLUDE,
)


# ============================================================================
# WebsiteAuditor Tests
# ============================================================================

class TestWebsiteAuditor(unittest.TestCase):
    """Tests for WebsiteAuditor class."""

    def setUp(self):
        """Set up test with WebsiteAuditor instance."""
        self.auditor = WebsiteAuditor()

    def test_auditor_initialization(self):
        """Test auditor initializes correctly."""
        self.assertIsNotNone(self.auditor.session)
        self.assertIn('User-Agent', self.auditor.session.headers)

    def test_audit_website_empty_url(self):
        """Test audit with empty URL."""
        result = self.auditor.audit_website('')

        self.assertEqual(result['url'], '')
        self.assertIn('No URL provided', result['errors'])

    def test_audit_website_none_url(self):
        """Test audit with None URL."""
        result = self.auditor.audit_website(None)

        self.assertIn('No URL provided', result['errors'])

    @patch.object(WebsiteAuditor, '_check_ssl')
    @patch.object(WebsiteAuditor, '_detect_hosting')
    @patch('requests.Session.get')
    def test_audit_website_success(self, mock_get, mock_hosting, mock_ssl):
        """Test successful website audit."""
        mock_ssl.return_value = {
            'ssl_valid': True,
            'ssl_expiry': date(2027, 1, 1),
            'ssl_issuer': "Let's Encrypt",
        }
        mock_hosting.return_value = {
            'hosting_provider': 'OVH',
            'hosting_ip': '51.38.1.1',
        }

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.url = 'https://example.com/'
        mock_response.headers = {
            'Server': 'nginx/1.18.0',
            'Last-Modified': 'Tue, 07 Jan 2026 10:00:00 GMT',
        }
        mock_response.text = '''
        <html>
        <head>
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <meta name="author" content="Test Author">
            <meta name="generator" content="WordPress 6.0">
        </head>
        <body>
            <a href="https://facebook.com/testpage">Facebook</a>
        </body>
        </html>
        '''
        mock_get.return_value = mock_response

        result = self.auditor.audit_website('https://example.com')

        self.assertEqual(result['http_status'], 200)
        self.assertTrue(result['has_ssl'])
        self.assertTrue(result['ssl_valid'])
        self.assertTrue(result['has_viewport_meta'])
        self.assertTrue(result['is_mobile_friendly'])
        self.assertEqual(result['site_author'], 'Test Author')
        self.assertEqual(result['site_generator'], 'WordPress 6.0')

    @patch('requests.Session.get')
    def test_audit_website_ssl_error_fallback(self, mock_get):
        """Test SSL error with HTTP fallback."""
        # First call (HTTPS) raises SSL error
        # Second call (HTTP) succeeds
        mock_http_response = Mock()
        mock_http_response.status_code = 200
        mock_http_response.url = 'http://example.com/'
        mock_http_response.text = '<html><head></head><body></body></html>'
        mock_http_response.headers = {}

        import requests
        mock_get.side_effect = [
            requests.exceptions.SSLError('Certificate verify failed'),
            mock_http_response,
        ]

        result = self.auditor.audit_website('https://example.com')

        self.assertEqual(result['http_status'], 200)
        self.assertFalse(result['has_ssl'])
        self.assertFalse(result['ssl_valid'])
        self.assertIn('SSL Error', result['errors'][0])

    def test_audit_website_normalizes_url(self):
        """Test URL normalization adds https://."""
        with patch.object(self.auditor, '_check_ssl') as mock_ssl, \
             patch.object(self.auditor, '_detect_hosting') as mock_hosting, \
             patch('requests.Session.get') as mock_get:

            mock_ssl.return_value = {}
            mock_hosting.return_value = {}
            mock_response = Mock()
            mock_response.status_code = 200
            mock_response.url = 'https://example.com/'
            mock_response.text = '<html></html>'
            mock_response.headers = {}
            mock_get.return_value = mock_response

            result = self.auditor.audit_website('example.com')

            # Verify the URL was normalized before making request
            mock_get.assert_called()


class TestWebsiteAuditorSSL(unittest.TestCase):
    """Tests for SSL checking functionality."""

    def setUp(self):
        """Set up test with WebsiteAuditor instance."""
        self.auditor = WebsiteAuditor()

    @patch('socket.create_connection')
    @patch('ssl.create_default_context')
    def test_check_ssl_valid_certificate(self, mock_context, mock_connection):
        """Test SSL check with valid certificate."""
        mock_sock = MagicMock()
        mock_connection.return_value.__enter__.return_value = mock_sock

        mock_ssock = MagicMock()
        mock_ssock.getpeercert.return_value = {
            'notAfter': 'Jan  1 00:00:00 2027 GMT',
            'issuer': ((('organizationName', "Let's Encrypt"),),),
        }
        mock_context.return_value.wrap_socket.return_value.__enter__.return_value = mock_ssock

        result = self.auditor._check_ssl('example.com')

        self.assertTrue(result['ssl_valid'])
        self.assertEqual(result['ssl_expiry'], date(2027, 1, 1))
        self.assertEqual(result['ssl_issuer'], "Let's Encrypt")

    @patch('socket.create_connection')
    def test_check_ssl_connection_error(self, mock_connection):
        """Test SSL check with connection error."""
        mock_connection.side_effect = ConnectionRefusedError('Connection refused')

        result = self.auditor._check_ssl('example.com')

        self.assertFalse(result['ssl_valid'])


class TestWebsiteAuditorHosting(unittest.TestCase):
    """Tests for hosting detection functionality."""

    def setUp(self):
        """Set up test with WebsiteAuditor instance."""
        self.auditor = WebsiteAuditor()

    @patch('socket.gethostbyname')
    def test_detect_hosting_by_ip_prefix(self, mock_gethostbyname):
        """Test hosting detection by IP prefix."""
        mock_gethostbyname.return_value = '51.38.123.45'

        result = self.auditor._detect_hosting('example.com')

        self.assertEqual(result['hosting_ip'], '51.38.123.45')
        self.assertEqual(result['hosting_provider'], 'OVH')

    @patch('socket.gethostbyname')
    def test_detect_hosting_cloudflare(self, mock_gethostbyname):
        """Test Cloudflare hosting detection."""
        mock_gethostbyname.return_value = '104.16.123.45'

        result = self.auditor._detect_hosting('example.com')

        self.assertEqual(result['hosting_provider'], 'Cloudflare')

    @patch('socket.gethostbyname')
    @patch('socket.gethostbyaddr')
    def test_detect_hosting_by_reverse_dns(self, mock_reverse, mock_gethostbyname):
        """Test hosting detection by reverse DNS."""
        mock_gethostbyname.return_value = '192.168.1.1'  # Unknown IP
        mock_reverse.return_value = ('server.nazwa.pl', [], [])

        result = self.auditor._detect_hosting('example.com')

        self.assertEqual(result['hosting_provider'], 'nazwa.pl')


class TestWebsiteAuditorHTMLParsing(unittest.TestCase):
    """Tests for HTML parsing functionality."""

    def setUp(self):
        """Set up test with WebsiteAuditor instance."""
        self.auditor = WebsiteAuditor()

    def test_parse_html_viewport_meta(self):
        """Test viewport meta detection."""
        html = '''
        <html>
        <head>
            <meta name="viewport" content="width=device-width, initial-scale=1">
        </head>
        <body></body>
        </html>
        '''
        result = self.auditor._parse_html(html)

        self.assertTrue(result['has_viewport_meta'])
        self.assertTrue(result['is_mobile_friendly'])

    def test_parse_html_no_viewport(self):
        """Test detection when viewport is missing."""
        html = '<html><head></head><body></body></html>'
        result = self.auditor._parse_html(html)

        self.assertFalse(result['has_viewport_meta'])
        self.assertFalse(result['is_mobile_friendly'])

    def test_parse_html_author_meta(self):
        """Test author meta extraction."""
        html = '''
        <html>
        <head>
            <meta name="author" content="Test Company">
        </head>
        <body></body>
        </html>
        '''
        result = self.auditor._parse_html(html)

        self.assertEqual(result['site_author'], 'Test Company')

    def test_parse_html_generator_meta(self):
        """Test generator/CMS detection."""
        html = '''
        <html>
        <head>
            <meta name="generator" content="WordPress 6.4">
        </head>
        <body></body>
        </html>
        '''
        result = self.auditor._parse_html(html)

        self.assertEqual(result['site_generator'], 'WordPress 6.4')

    def test_parse_html_footer_author(self):
        """Test author extraction from footer."""
        html = '''
        <html>
        <head></head>
        <body>
            <footer>
                Wykonanie: Test Agency
            </footer>
        </body>
        </html>
        '''
        result = self.auditor._parse_html(html)

        self.assertIsNotNone(result['site_author'])
        self.assertIn('Test Agency', result['site_author'])

    def test_parse_html_social_media_facebook(self):
        """Test Facebook link extraction."""
        html = '''
        <html>
        <head></head>
        <body>
            <a href="https://facebook.com/testcompany">Facebook</a>
        </body>
        </html>
        '''
        result = self.auditor._parse_html(html)

        self.assertIn('facebook', result['social_media_links'])
        self.assertEqual(result['social_media_links']['facebook'], 'https://facebook.com/testcompany')

    def test_parse_html_social_media_instagram(self):
        """Test Instagram link extraction."""
        html = '''
        <html>
        <head></head>
        <body>
            <a href="https://instagram.com/testcompany">Instagram</a>
        </body>
        </html>
        '''
        result = self.auditor._parse_html(html)

        self.assertIn('instagram', result['social_media_links'])

    def test_parse_html_social_media_excludes_share_links(self):
        """Test that share/sharer links are excluded."""
        html = '''
        <html>
        <head></head>
        <body>
            <a href="https://facebook.com/sharer">Share</a>
            <a href="https://facebook.com/share">Share</a>
        </body>
        </html>
        '''
        result = self.auditor._parse_html(html)

        # Should not extract share links
        self.assertEqual(result['social_media_links'], {})

    def test_parse_html_all_social_platforms(self):
        """Test extraction of all social media platforms."""
        html = '''
        <html>
        <head></head>
        <body>
            <a href="https://facebook.com/testfb">FB</a>
            <a href="https://instagram.com/testig">IG</a>
            <a href="https://youtube.com/@testyt">YT</a>
            <a href="https://linkedin.com/company/testli">LI</a>
            <a href="https://tiktok.com/@testtk">TT</a>
            <a href="https://twitter.com/testtw">TW</a>
        </body>
        </html>
        '''
        result = self.auditor._parse_html(html)

        self.assertIn('facebook', result['social_media_links'])
        self.assertIn('instagram', result['social_media_links'])
        self.assertIn('youtube', result['social_media_links'])
        self.assertIn('linkedin', result['social_media_links'])
        self.assertIn('tiktok', result['social_media_links'])
        self.assertIn('twitter', result['social_media_links'])


# ============================================================================
# GooglePlacesSearcher Tests
# ============================================================================

class TestGooglePlacesSearcher(unittest.TestCase):
    """Tests for GooglePlacesSearcher class."""

    def setUp(self):
        """Set up test with GooglePlacesSearcher instance."""
        self.searcher = GooglePlacesSearcher(api_key='test_api_key')

    def test_searcher_initialization_with_api_key(self):
        """Test searcher initializes with provided API key."""
        searcher = GooglePlacesSearcher(api_key='my_api_key')
        self.assertEqual(searcher.api_key, 'my_api_key')

    @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'env_api_key'})
    def test_searcher_initialization_from_env(self):
        """Test searcher initializes from environment variable."""
        searcher = GooglePlacesSearcher()
        self.assertEqual(searcher.api_key, 'env_api_key')

    def test_searcher_initialization_no_api_key(self):
        """Test searcher handles missing API key."""
        with patch.dict('os.environ', {}, clear=True):
            searcher = GooglePlacesSearcher(api_key=None)
            self.assertIsNone(searcher.api_key)


class TestGooglePlacesSearcherFindPlace(unittest.TestCase):
    """Tests for GooglePlacesSearcher.find_place method."""

    def setUp(self):
        """Set up test with GooglePlacesSearcher instance."""
        self.searcher = GooglePlacesSearcher(api_key='test_api_key')

    def test_find_place_no_api_key(self):
        """Test find_place returns None without API key."""
        searcher = GooglePlacesSearcher(api_key=None)
        result = searcher.find_place('Test Company')
        self.assertIsNone(result)

    @patch('requests.Session.get')
    def test_find_place_success(self, mock_get):
        """Test successful place search."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'status': 'OK',
            'candidates': [
                {
                    'place_id': 'ChIJN1t_tDeuEmsRUsoyG83frY4',
                    'name': 'Test Company',
                    'formatted_address': 'Wejherowo, Poland',
                }
            ]
        }
        mock_response.raise_for_status = Mock()
        mock_get.return_value = mock_response

        result = self.searcher.find_place('Test Company', 'Wejherowo')

        self.assertEqual(result, 'ChIJN1t_tDeuEmsRUsoyG83frY4')
        mock_get.assert_called_once()

        # Verify API call parameters
        call_args = mock_get.call_args
        params = call_args.kwargs.get('params', call_args[1].get('params', {}))
        self.assertEqual(params['inputtype'], 'textquery')
        self.assertIn('Test Company', params['input'])
        self.assertEqual(params['language'], 'pl')
        self.assertEqual(params['key'], 'test_api_key')

    @patch('requests.Session.get')
    def test_find_place_zero_results(self, mock_get):
        """Test find_place with no results."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'status': 'ZERO_RESULTS',
            'candidates': []
        }
        mock_response.raise_for_status = Mock()
        mock_get.return_value = mock_response

        result = self.searcher.find_place('Nonexistent Company')

        self.assertIsNone(result)

    @patch('requests.Session.get')
    def test_find_place_api_error(self, mock_get):
        """Test find_place handles API errors."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'status': 'REQUEST_DENIED',
            'error_message': 'Invalid API key',
        }
        mock_response.raise_for_status = Mock()
        mock_get.return_value = mock_response

        result = self.searcher.find_place('Test Company')

        self.assertIsNone(result)

    @patch('requests.Session.get')
    def test_find_place_timeout(self, mock_get):
        """Test find_place handles timeout."""
        import requests
        mock_get.side_effect = requests.exceptions.Timeout('Connection timed out')

        result = self.searcher.find_place('Test Company')

        self.assertIsNone(result)

    @patch('requests.Session.get')
    def test_find_place_request_exception(self, mock_get):
        """Test find_place handles request exceptions."""
        import requests
        mock_get.side_effect = requests.exceptions.RequestException('Network error')

        result = self.searcher.find_place('Test Company')

        self.assertIsNone(result)


class TestGooglePlacesSearcherGetDetails(unittest.TestCase):
    """Tests for GooglePlacesSearcher.get_place_details method."""

    def setUp(self):
        """Set up test with GooglePlacesSearcher instance."""
        self.searcher = GooglePlacesSearcher(api_key='test_api_key')

    def test_get_place_details_no_api_key(self):
        """Test get_place_details returns empty dict without API key."""
        searcher = GooglePlacesSearcher(api_key=None)
        result = searcher.get_place_details('some_place_id')

        self.assertIsNone(result['google_rating'])
        self.assertIsNone(result['google_reviews_count'])

    def test_get_place_details_no_place_id(self):
        """Test get_place_details returns empty dict without place_id."""
        result = self.searcher.get_place_details(None)

        self.assertIsNone(result['google_rating'])
        self.assertIsNone(result['google_reviews_count'])

    def test_get_place_details_empty_place_id(self):
        """Test get_place_details returns empty dict with empty place_id."""
        result = self.searcher.get_place_details('')

        self.assertIsNone(result['google_rating'])
        self.assertIsNone(result['google_reviews_count'])

    @patch('requests.Session.get')
    def test_get_place_details_success(self, mock_get):
        """Test successful place details retrieval."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'status': 'OK',
            'result': {
                'name': 'Test Company',
                'rating': 4.5,
                'user_ratings_total': 123,
                'opening_hours': {
                    'weekday_text': [
                        'Monday: 8:00 AM – 6:00 PM',
                        'Tuesday: 8:00 AM – 6:00 PM',
                    ],
                    'open_now': True,
                    'periods': [],
                },
                'business_status': 'OPERATIONAL',
                'formatted_phone_number': '+48 58 123 4567',
                'website': 'https://example.com',
            }
        }
        mock_response.raise_for_status = Mock()
        mock_get.return_value = mock_response

        result = self.searcher.get_place_details('test_place_id')

        self.assertEqual(result['google_rating'], 4.5)
        self.assertEqual(result['google_reviews_count'], 123)
        self.assertEqual(result['business_status'], 'OPERATIONAL')
        self.assertEqual(result['formatted_phone'], '+48 58 123 4567')
        self.assertEqual(result['website'], 'https://example.com')
        self.assertIsNotNone(result['opening_hours'])
        self.assertTrue(result['opening_hours']['open_now'])

    @patch('requests.Session.get')
    def test_get_place_details_partial_data(self, mock_get):
        """Test place details with partial data."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'status': 'OK',
            'result': {
                'name': 'Test Company',
                'rating': 3.8,
                # No review count, opening hours, etc.
            }
        }
        mock_response.raise_for_status = Mock()
        mock_get.return_value = mock_response

        result = self.searcher.get_place_details('test_place_id')

        self.assertEqual(result['google_rating'], 3.8)
        self.assertIsNone(result['google_reviews_count'])
        self.assertIsNone(result['opening_hours'])
        self.assertIsNone(result['business_status'])

    @patch('requests.Session.get')
    def test_get_place_details_api_error(self, mock_get):
        """Test get_place_details handles API errors."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'status': 'NOT_FOUND',
        }
        mock_response.raise_for_status = Mock()
        mock_get.return_value = mock_response

        result = self.searcher.get_place_details('invalid_place_id')

        self.assertIsNone(result['google_rating'])
        self.assertIsNone(result['google_reviews_count'])

    @patch('requests.Session.get')
    def test_get_place_details_timeout(self, mock_get):
        """Test get_place_details handles timeout."""
        import requests
        mock_get.side_effect = requests.exceptions.Timeout('Connection timed out')

        result = self.searcher.get_place_details('test_place_id')

        self.assertIsNone(result['google_rating'])
        self.assertIsNone(result['google_reviews_count'])

    @patch('requests.Session.get')
    def test_get_place_details_correct_fields_requested(self, mock_get):
        """Test that correct fields are requested from API."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {'status': 'OK', 'result': {}}
        mock_response.raise_for_status = Mock()
        mock_get.return_value = mock_response

        self.searcher.get_place_details('test_place_id')

        # Verify API call includes required fields
        call_args = mock_get.call_args
        params = call_args.kwargs.get('params', call_args[1].get('params', {}))
        fields = params['fields'].split(',')

        self.assertIn('rating', fields)
        self.assertIn('user_ratings_total', fields)
        self.assertIn('opening_hours', fields)
        self.assertIn('business_status', fields)
        self.assertIn('formatted_phone_number', fields)
        self.assertIn('website', fields)


class TestGooglePlacesSearcherIntegration(unittest.TestCase):
    """Integration tests for GooglePlacesSearcher."""

    def setUp(self):
        """Set up test with GooglePlacesSearcher instance."""
        self.searcher = GooglePlacesSearcher(api_key='test_api_key')

    @patch('requests.Session.get')
    def test_find_and_get_details_workflow(self, mock_get):
        """Test complete workflow: find place, then get details."""
        # Mock find_place response
        find_response = Mock()
        find_response.status_code = 200
        find_response.json.return_value = {
            'status': 'OK',
            'candidates': [
                {
                    'place_id': 'test_place_id_123',
                    'name': 'PIXLAB Sp. z o.o.',
                    'formatted_address': 'Wejherowo, Poland',
                }
            ]
        }
        find_response.raise_for_status = Mock()

        # Mock get_place_details response
        details_response = Mock()
        details_response.status_code = 200
        details_response.json.return_value = {
            'status': 'OK',
            'result': {
                'name': 'PIXLAB Sp. z o.o.',
                'rating': 4.9,
                'user_ratings_total': 45,
                'business_status': 'OPERATIONAL',
                'opening_hours': {
                    'weekday_text': ['Monday: 9:00 AM – 5:00 PM'],
                    'open_now': False,
                },
            }
        }
        details_response.raise_for_status = Mock()

        mock_get.side_effect = [find_response, details_response]

        # Execute workflow
        place_id = self.searcher.find_place('PIXLAB', 'Wejherowo')
        self.assertEqual(place_id, 'test_place_id_123')

        details = self.searcher.get_place_details(place_id)
        self.assertEqual(details['google_rating'], 4.9)
        self.assertEqual(details['google_reviews_count'], 45)
        self.assertEqual(details['business_status'], 'OPERATIONAL')


# ============================================================================
# BraveSearcher Tests
# ============================================================================

class TestBraveSearcher(unittest.TestCase):
    """Tests for BraveSearcher class."""

    def setUp(self):
        """Set up test with BraveSearcher instance."""
        self.searcher = BraveSearcher(api_key='test_brave_api_key')

    def test_searcher_initialization_with_api_key(self):
        """Test searcher initializes with provided API key."""
        searcher = BraveSearcher(api_key='my_brave_key')
        self.assertEqual(searcher.api_key, 'my_brave_key')

    @patch.dict('os.environ', {'BRAVE_API_KEY': 'env_brave_key'})
    def test_searcher_initialization_from_env(self):
        """Test searcher initializes from environment variable."""
        searcher = BraveSearcher()
        self.assertEqual(searcher.api_key, 'env_brave_key')


class TestBraveSearcherGoogleReviews(unittest.TestCase):
    """Tests for BraveSearcher Google reviews functionality."""

    def setUp(self):
        """Set up test with BraveSearcher instance."""
        self.searcher = BraveSearcher(api_key='test_brave_api_key')

    @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_places_key'})
    @patch.object(GooglePlacesSearcher, 'find_place')
    @patch.object(GooglePlacesSearcher, 'get_place_details')
    def test_search_google_reviews(self, mock_details, mock_find):
        """Test successful Google reviews retrieval via Places API."""
        # Mock successful place lookup
        mock_find.return_value = 'ChIJN1t_tDeuEmsRUsoyG83frY4'

        # Mock complete place details response
        mock_details.return_value = {
            'google_rating': 4.7,
            'google_reviews_count': 156,
            'opening_hours': {
                'weekday_text': [
                    'Monday: 8:00 AM – 5:00 PM',
                    'Tuesday: 8:00 AM – 5:00 PM',
                    'Wednesday: 8:00 AM – 5:00 PM',
                    'Thursday: 8:00 AM – 5:00 PM',
                    'Friday: 8:00 AM – 4:00 PM',
                    'Saturday: Closed',
                    'Sunday: Closed',
                ],
                'open_now': True,
                'periods': [],
            },
            'business_status': 'OPERATIONAL',
            'formatted_phone': '+48 58 123 4567',
            'website': 'https://example.com',
        }

        result = self.searcher.search_google_reviews('Test Company Sp. z o.o.', 'Wejherowo')

        # Verify all fields are correctly returned
        self.assertEqual(result['google_rating'], 4.7)
        self.assertEqual(result['google_reviews_count'], 156)
        self.assertIsNotNone(result['opening_hours'])
        self.assertTrue(result['opening_hours']['open_now'])
        self.assertEqual(len(result['opening_hours']['weekday_text']), 7)
        self.assertEqual(result['business_status'], 'OPERATIONAL')

        # Verify correct API calls were made
        mock_find.assert_called_once_with('Test Company Sp. z o.o.', 'Wejherowo')
        mock_details.assert_called_once_with('ChIJN1t_tDeuEmsRUsoyG83frY4')

    @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_places_key'})
    @patch.object(GooglePlacesSearcher, 'find_place')
    @patch.object(GooglePlacesSearcher, 'get_place_details')
    def test_search_google_reviews_uses_places_api(self, mock_details, mock_find):
        """Test that search_google_reviews uses Google Places API when available."""
        mock_find.return_value = 'test_place_id'
        mock_details.return_value = {
            'google_rating': 4.5,
            'google_reviews_count': 100,
            'opening_hours': {'open_now': True},
            'business_status': 'OPERATIONAL',
        }

        result = self.searcher.search_google_reviews('Test Company', 'Wejherowo')

        self.assertEqual(result['google_rating'], 4.5)
        self.assertEqual(result['google_reviews_count'], 100)
        mock_find.assert_called_once_with('Test Company', 'Wejherowo')
        mock_details.assert_called_once_with('test_place_id')

    @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_places_key'})
    @patch.object(GooglePlacesSearcher, 'find_place')
    def test_search_google_reviews_no_place_found(self, mock_find):
        """Test search_google_reviews when no place is found."""
        mock_find.return_value = None

        result = self.searcher.search_google_reviews('Unknown Company', 'Wejherowo')

        self.assertIsNone(result['google_rating'])
        self.assertIsNone(result['google_reviews_count'])

    @patch.dict('os.environ', {}, clear=True)
    def test_search_google_reviews_no_api_keys(self):
        """Test search_google_reviews without any API keys."""
        searcher = BraveSearcher(api_key=None)

        result = searcher.search_google_reviews('Test Company')

        self.assertIsNone(result['google_rating'])
        self.assertIsNone(result['google_reviews_count'])

    @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_places_key'})
    @patch.object(GooglePlacesSearcher, 'find_place')
    @patch.object(GooglePlacesSearcher, 'get_place_details')
    def test_search_google_reviews_error(self, mock_details, mock_find):
        """Test search_google_reviews handles API errors gracefully."""
        import requests

        # Mock find_place to succeed but get_place_details to raise an error
        mock_find.return_value = 'test_place_id'
        mock_details.side_effect = requests.exceptions.RequestException('API connection failed')

        result = self.searcher.search_google_reviews('Test Company', 'Wejherowo')

        # Should handle error gracefully and return None values
        self.assertIsNone(result['google_rating'])
        self.assertIsNone(result['google_reviews_count'])

        # Verify the API calls were made
        mock_find.assert_called_once_with('Test Company', 'Wejherowo')
        mock_details.assert_called_once_with('test_place_id')


class TestBraveSearcherBraveReviews(unittest.TestCase):
    """Tests for Brave Search fallback for reviews."""

    def setUp(self):
        """Set up test with BraveSearcher instance."""
        self.searcher = BraveSearcher(api_key='test_brave_api_key')

    @patch('requests.Session.get')
    def test_search_brave_for_reviews_success(self, mock_get):
        """Test successful review extraction from Brave search."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'web': {
                'results': [
                    {
                        'title': 'Test Company - Google Maps',
                        'description': 'Ocena: 4,5 (123 opinii) - Test Company',
                    }
                ]
            }
        }
        mock_response.raise_for_status = Mock()
        mock_get.return_value = mock_response

        result = self.searcher._search_brave_for_reviews('Test Company', 'Wejherowo')

        self.assertIsNotNone(result)
        self.assertEqual(result['google_rating'], 4.5)
        self.assertEqual(result['google_reviews_count'], 123)

    @patch('requests.Session.get')
    def test_search_brave_for_reviews_no_rating_found(self, mock_get):
        """Test Brave search when no rating is found."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'web': {
                'results': [
                    {
                        'title': 'Test Company Website',
                        'description': 'Some description without rating',
                    }
                ]
            }
        }
        mock_response.raise_for_status = Mock()
        mock_get.return_value = mock_response

        result = self.searcher._search_brave_for_reviews('Test Company', 'Wejherowo')

        self.assertIsNone(result)

    def test_search_brave_for_reviews_no_api_key(self):
        """Test Brave search returns None without API key."""
        searcher = BraveSearcher(api_key=None)
        result = searcher._search_brave_for_reviews('Test Company', 'Wejherowo')
        self.assertIsNone(result)

    @patch('requests.Session.get')
    def test_search_brave_for_reviews_timeout(self, mock_get):
        """Test Brave search handles timeout."""
        import requests
        mock_get.side_effect = requests.exceptions.Timeout('Connection timed out')

        result = self.searcher._search_brave_for_reviews('Test Company', 'Wejherowo')

        self.assertIsNone(result)


# ============================================================================
# SocialMediaAuditor Tests
# ============================================================================

class TestSocialMediaAuditor(unittest.TestCase):
    """Tests for SocialMediaAuditor class."""

    @patch('social_media_audit.create_engine')
    @patch('social_media_audit.sessionmaker')
    def setUp(self, mock_sessionmaker, mock_engine):
        """Set up test with mocked database."""
        self.mock_session = MagicMock()
        mock_sessionmaker.return_value = Mock(return_value=self.mock_session)
        self.auditor = SocialMediaAuditor(database_url='postgresql://test:test@localhost/test')

    @patch('social_media_audit.create_engine')
    @patch('social_media_audit.sessionmaker')
    def test_auditor_initialization(self, mock_sessionmaker, mock_engine):
        """Test auditor initializes correctly."""
        auditor = SocialMediaAuditor()
        self.assertIsNotNone(auditor.website_auditor)
        self.assertIsNotNone(auditor.brave_searcher)

    @patch('social_media_audit.create_engine')
    @patch('social_media_audit.sessionmaker')
    @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_key'})
    def test_auditor_initializes_google_places(self, mock_sessionmaker, mock_engine):
        """Test auditor initializes Google Places searcher when API key available."""
        auditor = SocialMediaAuditor()
        self.assertIsNotNone(auditor.google_places_searcher)


class TestSocialMediaAuditorGetCompanies(unittest.TestCase):
    """Tests for SocialMediaAuditor.get_companies method."""

    @patch('social_media_audit.create_engine')
    @patch('social_media_audit.sessionmaker')
    def setUp(self, mock_sessionmaker, mock_engine):
        """Set up test with mocked database."""
        self.mock_session = MagicMock()
        mock_sessionmaker.return_value = Mock(return_value=MagicMock(
            __enter__=Mock(return_value=self.mock_session),
            __exit__=Mock(return_value=False)
        ))
        self.auditor = SocialMediaAuditor()

    def test_get_companies_by_ids(self):
        """Test fetching companies by specific IDs."""
        mock_result = [
            Mock(_mapping={'id': 1, 'name': 'Company A', 'slug': 'company-a', 'website': 'https://a.com', 'address_city': 'Wejherowo'}),
            Mock(_mapping={'id': 2, 'name': 'Company B', 'slug': 'company-b', 'website': 'https://b.com', 'address_city': 'Wejherowo'}),
        ]
        self.mock_session.execute.return_value = mock_result

        companies = self.auditor.get_companies(company_ids=[1, 2])

        # Verify query was called
        self.mock_session.execute.assert_called_once()


class TestSocialMediaAuditorAuditCompany(unittest.TestCase):
    """Tests for SocialMediaAuditor.audit_company method."""

    @patch('social_media_audit.create_engine')
    @patch('social_media_audit.sessionmaker')
    def setUp(self, mock_sessionmaker, mock_engine):
        """Set up test with mocked database."""
        mock_session = MagicMock()
        mock_sessionmaker.return_value = Mock(return_value=mock_session)
        self.auditor = SocialMediaAuditor()
        self.auditor.website_auditor = Mock()
        self.auditor.brave_searcher = Mock()
        self.auditor.google_places_searcher = None

    def test_audit_company_with_website(self):
        """Test auditing company with website."""
        company = {
            'id': 1,
            'name': 'Test Company',
            'slug': 'test-company',
            'website': 'https://example.com',
            'address_city': 'Wejherowo',
        }

        self.auditor.website_auditor.audit_website.return_value = {
            'http_status': 200,
            'has_ssl': True,
            'social_media_links': {'facebook': 'https://facebook.com/test'},
        }
        self.auditor.brave_searcher.search_social_media.return_value = {}
        self.auditor.brave_searcher.search_google_reviews.return_value = {
            'google_rating': 4.5,
            'google_reviews_count': 50,
        }

        result = self.auditor.audit_company(company)

        self.assertEqual(result['company_id'], 1)
        self.assertEqual(result['company_name'], 'Test Company')
        self.assertIn('facebook', result['social_media'])
        self.auditor.website_auditor.audit_website.assert_called_once_with('https://example.com')

    def test_audit_company_without_website(self):
        """Test auditing company without website."""
        company = {
            'id': 2,
            'name': 'No Website Company',
            'slug': 'no-website',
            'website': None,
            'address_city': 'Wejherowo',
        }

        self.auditor.brave_searcher.search_social_media.return_value = {
            'facebook': 'https://facebook.com/found',
        }
        self.auditor.brave_searcher.search_google_reviews.return_value = {}

        result = self.auditor.audit_company(company)

        self.assertEqual(result['company_id'], 2)
        self.assertIn('No website URL', result['website'].get('errors', []))
        self.assertIn('facebook', result['social_media'])

    @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_key'})
    def test_audit_company_uses_google_places(self):
        """Test auditing uses Google Places API when available."""
        company = {
            'id': 3,
            'name': 'Google Test',
            'slug': 'google-test',
            'website': 'https://example.com',
            'address_city': 'Wejherowo',
        }

        mock_places_searcher = Mock()
        mock_places_searcher.find_place.return_value = 'test_place_id'
        mock_places_searcher.get_place_details.return_value = {
            'google_rating': 4.8,
            'google_reviews_count': 200,
            'business_status': 'OPERATIONAL',
            'opening_hours': {'open_now': True},
        }
        self.auditor.google_places_searcher = mock_places_searcher
        self.auditor.website_auditor.audit_website.return_value = {'social_media_links': {}}
        self.auditor.brave_searcher.search_social_media.return_value = {}

        result = self.auditor.audit_company(company)

        self.assertEqual(result['google_reviews']['google_rating'], 4.8)
        self.assertEqual(result['google_reviews']['google_reviews_count'], 200)
        mock_places_searcher.find_place.assert_called_once_with('Google Test', 'Wejherowo')


class TestSocialMediaAuditorSaveResult(unittest.TestCase):
    """Tests for SocialMediaAuditor.save_audit_result method."""

    @patch('social_media_audit.create_engine')
    @patch('social_media_audit.sessionmaker')
    def setUp(self, mock_sessionmaker, mock_engine):
        """Set up test with mocked database."""
        self.mock_session = MagicMock()
        mock_sessionmaker.return_value = Mock(return_value=MagicMock(
            __enter__=Mock(return_value=self.mock_session),
            __exit__=Mock(return_value=False)
        ))
        self.auditor = SocialMediaAuditor()

    def test_save_audit_result_success(self):
        """Test saving audit result successfully."""
        result = {
            'company_id': 1,
            'company_name': 'Test Company',
            'audit_date': datetime.now(),
            'website': {
                'url': 'https://example.com',
                'http_status': 200,
                'has_ssl': True,
            },
            'social_media': {
                'facebook': 'https://facebook.com/test',
            },
            'google_reviews': {
                'google_rating': 4.5,
                'google_reviews_count': 50,
            },
        }

        success = self.auditor.save_audit_result(result)

        self.assertTrue(success)
        # Verify execute was called for website and social media
        self.assertTrue(self.mock_session.execute.called)
        self.mock_session.commit.assert_called_once()


# ============================================================================
# Constants and Patterns Tests
# ============================================================================

class TestHostingProviders(unittest.TestCase):
    """Tests for hosting provider patterns."""

    def test_hosting_providers_structure(self):
        """Test that HOSTING_PROVIDERS has correct structure."""
        self.assertIsInstance(HOSTING_PROVIDERS, dict)
        for provider, patterns in HOSTING_PROVIDERS.items():
            self.assertIsInstance(provider, str)
            self.assertIsInstance(patterns, list)
            for pattern in patterns:
                self.assertIsInstance(pattern, str)

    def test_major_providers_included(self):
        """Test that major hosting providers are included."""
        expected_providers = ['OVH', 'Cloudflare', 'Google Cloud', 'AWS', 'nazwa.pl', 'home.pl']
        for provider in expected_providers:
            self.assertIn(provider, HOSTING_PROVIDERS)


class TestSocialMediaPatterns(unittest.TestCase):
    """Tests for social media URL patterns."""

    def test_social_media_patterns_structure(self):
        """Test that SOCIAL_MEDIA_PATTERNS has correct structure."""
        self.assertIsInstance(SOCIAL_MEDIA_PATTERNS, dict)
        expected_platforms = ['facebook', 'instagram', 'youtube', 'linkedin', 'tiktok', 'twitter']
        for platform in expected_platforms:
            self.assertIn(platform, SOCIAL_MEDIA_PATTERNS)

    def test_facebook_pattern_matches(self):
        """Test Facebook URL pattern matching."""
        import re
        patterns = SOCIAL_MEDIA_PATTERNS['facebook']

        test_urls = [
            ('https://facebook.com/testpage', 'testpage'),
            ('https://www.facebook.com/testpage', 'testpage'),
            ('https://fb.com/testpage', 'testpage'),
        ]

        for url, expected_match in test_urls:
            matched = False
            for pattern in patterns:
                match = re.search(pattern, url, re.IGNORECASE)
                if match:
                    self.assertEqual(match.group(1), expected_match)
                    matched = True
                    break
            self.assertTrue(matched, f"Pattern should match {url}")

    def test_instagram_pattern_matches(self):
        """Test Instagram URL pattern matching."""
        import re
        patterns = SOCIAL_MEDIA_PATTERNS['instagram']

        test_urls = [
            ('https://instagram.com/testaccount', 'testaccount'),
            ('https://www.instagram.com/testaccount', 'testaccount'),
        ]

        for url, expected_match in test_urls:
            matched = False
            for pattern in patterns:
                match = re.search(pattern, url, re.IGNORECASE)
                if match:
                    self.assertEqual(match.group(1), expected_match)
                    matched = True
                    break
            self.assertTrue(matched, f"Pattern should match {url}")


class TestSocialMediaExcludes(unittest.TestCase):
    """Tests for social media exclusion patterns."""

    def test_excludes_structure(self):
        """Test that SOCIAL_MEDIA_EXCLUDE has correct structure."""
        self.assertIsInstance(SOCIAL_MEDIA_EXCLUDE, dict)

    def test_facebook_excludes_share_links(self):
        """Test that Facebook share links are excluded."""
        excludes = SOCIAL_MEDIA_EXCLUDE['facebook']
        self.assertIn('sharer', excludes)
        self.assertIn('share', excludes)

    def test_twitter_excludes_intent(self):
        """Test that Twitter intent links are excluded."""
        excludes = SOCIAL_MEDIA_EXCLUDE['twitter']
        self.assertIn('intent', excludes)
        self.assertIn('share', excludes)


# ============================================================================
# Run Tests
# ============================================================================

if __name__ == '__main__':
    # Run with verbose output
    unittest.main(verbosity=2)
