"""
Tests for LinkerService
"""
from unittest.mock import Mock, patch, MagicMock
from django.test import TestCase
from igny8_core.business.content.models import Content
from igny8_core.business.linking.services.linker_service import LinkerService
from igny8_core.business.billing.exceptions import InsufficientCreditsError
from igny8_core.api.tests.test_integration_base import IntegrationTestBase


class LinkerServiceTests(IntegrationTestBase):
    """Tests for LinkerService"""
    
    def setUp(self):
        super().setUp()
        self.service = LinkerService()
        
        # Create test content
        self.content = Content.objects.create(
            account=self.account,
            site=self.site,
            sector=self.sector,
            title="Test Content",
            html_content="<p>This is test content with some keywords.</p>",
            primary_keyword="test keyword",
            word_count=100,
            status='draft'
        )
        
        # Create another content for linking
        self.target_content = Content.objects.create(
            account=self.account,
            site=self.site,
            sector=self.sector,
            title="Target Content",
            html_content="<p>Target content for linking.</p>",
            primary_keyword="test keyword",
            word_count=150,
            status='draft'
        )
    
    @patch('igny8_core.business.linking.services.linker_service.CreditService.check_credits')
    @patch('igny8_core.business.linking.services.linker_service.CandidateEngine.find_candidates')
    @patch('igny8_core.business.linking.services.linker_service.InjectionEngine.inject_links')
    @patch('igny8_core.business.linking.services.linker_service.CreditService.deduct_credits_for_operation')
    def test_process_single_content(self, mock_deduct, mock_inject, mock_find, mock_check):
        """Test processing single content for linking"""
        # Setup mocks
        mock_check.return_value = True
        mock_find.return_value = [{
            'content_id': self.target_content.id,
            'title': 'Target Content',
            'url': '/content/2/',
            'relevance_score': 50,
            'anchor_text': 'test keyword'
        }]
        mock_inject.return_value = {
            'html_content': '<p>This is test content with <a href="/content/2/">test keyword</a>.</p>',
            'links': [{
                'content_id': self.target_content.id,
                'anchor_text': 'test keyword',
                'url': '/content/2/'
            }],
            'links_added': 1
        }
        
        # Execute
        result = self.service.process(self.content.id)
        
        # Assertions
        self.assertEqual(result.id, self.content.id)
        self.assertEqual(result.linker_version, 1)
        self.assertEqual(len(result.internal_links), 1)
        mock_check.assert_called_once_with(self.account, 'linking')
        mock_deduct.assert_called_once()
    
    @patch('igny8_core.business.linking.services.linker_service.CreditService.check_credits')
    def test_process_insufficient_credits(self, mock_check):
        """Test that InsufficientCreditsError is raised when credits are insufficient"""
        mock_check.side_effect = InsufficientCreditsError("Insufficient credits")
        
        with self.assertRaises(InsufficientCreditsError):
            self.service.process(self.content.id)
    
    def test_process_content_not_found(self):
        """Test that ValueError is raised when content doesn't exist"""
        with self.assertRaises(ValueError):
            self.service.process(99999)
    
    @patch('igny8_core.business.linking.services.linker_service.LinkerService.process')
    def test_batch_process_multiple_content(self, mock_process):
        """Test batch processing multiple content items"""
        # Create additional content
        content2 = Content.objects.create(
            account=self.account,
            site=self.site,
            sector=self.sector,
            title="Content 2",
            html_content="<p>Content 2</p>",
            word_count=100,
            status='draft'
        )
        
        # Setup mock
        mock_process.side_effect = [self.content, content2]
        
        # Execute
        results = self.service.batch_process([self.content.id, content2.id])
        
        # Assertions
        self.assertEqual(len(results), 2)
        self.assertEqual(mock_process.call_count, 2)
    
    @patch('igny8_core.business.linking.services.linker_service.LinkerService.process')
    def test_batch_process_handles_partial_failure(self, mock_process):
        """Test batch processing handles partial failures"""
        # Setup mock to fail on second item
        mock_process.side_effect = [self.content, Exception("Processing failed")]
        
        # Execute
        results = self.service.batch_process([self.content.id, 99999])
        
        # Assertions - should continue processing other items
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0].id, self.content.id)
    
    @patch('igny8_core.business.linking.services.linker_service.CreditService.check_credits')
    @patch('igny8_core.business.linking.services.linker_service.CandidateEngine.find_candidates')
    def test_process_no_candidates_found(self, mock_find, mock_check):
        """Test processing when no candidates are found"""
        mock_check.return_value = True
        mock_find.return_value = []
        
        # Execute
        result = self.service.process(self.content.id)
        
        # Assertions - should return content unchanged
        self.assertEqual(result.id, self.content.id)
        self.assertEqual(result.linker_version, 0)  # Not incremented

