"""
Comprehensive Unit Tests for TeamService
Tests all team operations.
"""

import pytest
from unittest.mock import MagicMock, patch
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from app.services.team_service import TeamService
from app.services.base import ServiceConfig
from app.services.mocks import MockTeamRepository, MockCacheService


# ============================================================================
# FIXTURES
# ============================================================================

@pytest.fixture
def cache():
    """Create fresh cache for each test."""
    return MockCacheService()


@pytest.fixture
def repo(cache):
    """Create fresh repo with test data."""
    repo = MockTeamRepository()
    
    # Add test teams
    teams = [
        {'id': 1, 'abbrev': 'BAL', 'sname': 'Orioles', 'lname': 'Baltimore Orioles', 'gmid': 123, 'season': 10, 'manager1_id': 1},
        {'id': 2, 'abbrev': 'NYY', 'sname': 'Yankees', 'lname': 'New York Yankees', 'gmid': 456, 'season': 10, 'manager1_id': 2},
        {'id': 3, 'abbrev': 'BOS', 'sname': 'Red Sox', 'lname': 'Boston Red Sox', 'gmid': 789, 'season': 10, 'manager1_id': 3},
        {'id': 4, 'abbrev': 'BALIL', 'sname': 'Orioles IL', 'lname': 'Baltimore Orioles IL', 'gmid': 123, 'season': 10},
        {'id': 5, 'abbrev': 'OLD', 'sname': 'Old Team', 'lname': 'Old Team Full', 'gmid': 999, 'season': 5},
    ]
    
    for team in teams:
        repo.add_team(team)
    
    return repo


@pytest.fixture
def service(repo, cache):
    """Create service with mocks."""
    config = ServiceConfig(team_repo=repo, cache=cache)
    return TeamService(config=config)


# ============================================================================
# TEST CLASSES
# ============================================================================

class TestTeamServiceGetTeams:
    """Tests for get_teams method."""
    
    def test_get_all_season_teams(self, service, repo):
        """Get all teams for a season."""
        result = service.get_teams(season=10)
        
        assert result['count'] >= 4  # 4 season 10 teams
        assert len(result['teams']) >= 4
    
    def test_filter_by_abbrev(self, service):
        """Filter by team abbreviation."""
        result = service.get_teams(season=10, team_abbrev=['BAL'])
        
        assert result['count'] >= 1
        assert any(t.get('abbrev') == 'BAL' for t in result['teams'])
    
    def test_filter_by_multiple_abbrevs(self, service):
        """Filter by multiple abbreviations."""
        result = service.get_teams(season=10, team_abbrev=['BAL', 'NYY'])
        
        assert result['count'] >= 2
        for team in result['teams']:
            assert team.get('abbrev') in ['BAL', 'NYY']
    
    def test_filter_active_only(self, service):
        """Filter out IL teams."""
        result = service.get_teams(season=10, active_only=True)
        
        assert result['count'] >= 3  # Excludes BALIL
        assert all(not t.get('abbrev', '').endswith('IL') for t in result['teams'])
    
    def test_filter_by_manager(self, service):
        """Filter by manager ID."""
        result = service.get_teams(season=10, manager_id=[1])
        
        assert result['count'] >= 1
        assert any(t.get('manager1_id') == 1 for t in result['teams'])
    
    def test_sort_by_name(self, service):
        """Sort teams by abbreviation."""
        result = service.get_teams(season=10)
        
        # Teams should be ordered by ID (default)
        ids = [t.get('id') for t in result['teams']]
        assert ids == sorted(ids)


class TestTeamServiceGetTeam:
    """Tests for get_team method."""
    
    def test_get_existing_team(self, service):
        """Get existing team by ID."""
        result = service.get_team(1)
        
        assert result is not None
        assert result.get('id') == 1
        assert result.get('abbrev') == 'BAL'
    
    def test_get_nonexistent_team(self, service):
        """Get team that doesn't exist."""
        result = service.get_team(99999)
        
        assert result is None


class TestTeamServiceGetRoster:
    """Tests for get_team_roster method."""
    
    def test_get_current_roster(self, service):
        """Get current week roster."""
        # Note: This requires more complex mock setup for full testing
        # Simplified test for now
        pass
    
    def test_get_next_roster(self, service):
        """Get next week roster."""
        # Note: This requires more complex mock setup for full testing
        pass


class TestTeamServiceUpdate:
    """Tests for update_team method."""
    
    def test_patch_team_name(self, repo, cache):
        """Patch team's abbreviation."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        with patch.object(service, 'require_auth', return_value=True):
            result = service.update_team(1, {'abbrev': 'BAL2'}, 'valid_token')
        
        assert result.get('abbrev') == 'BAL2'
    
    def test_patch_team_manager(self, repo, cache):
        """Patch team's manager."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        with patch.object(service, 'require_auth', return_value=True):
            result = service.update_team(1, {'manager1_id': 10}, 'valid_token')
        
        assert result.get('manager1_id') == 10
    
    def test_patch_multiple_fields(self, repo, cache):
        """Patch multiple fields at once."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        updates = {
            'abbrev': 'BAL3',
            'sname': 'Birds',
            'color': '#FF0000'
        }
        
        with patch.object(service, 'require_auth', return_value=True):
            result = service.update_team(1, updates, 'valid_token')
        
        assert result.get('abbrev') == 'BAL3'
        assert result.get('sname') == 'Birds'
        assert result.get('color') == '#FF0000'
    
    def test_patch_nonexistent_team(self, repo, cache):
        """Patch fails for non-existent team."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        with patch.object(service, 'require_auth', return_value=True):
            with pytest.raises(Exception) as exc_info:
                service.update_team(99999, {'abbrev': 'TEST'}, 'valid_token')
        
        assert 'not found' in str(exc_info.value)
    
    def test_patch_requires_auth(self, repo, cache):
        """Patching requires authentication."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        with pytest.raises(Exception) as exc_info:
            service.update_team(1, {'abbrev': 'TEST'}, 'bad_token')
        
        assert exc_info.value.status_code == 401


class TestTeamServiceCreate:
    """Tests for create_teams method."""
    
    def test_create_single_team(self, repo, cache):
        """Create a single new team."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        new_team = [{
            'abbrev': 'CLE2',
            'sname': 'Guardians2',
            'lname': 'Cleveland Guardians 2',
            'gmid': 999,
            'season': 10
        }]
        
        with patch.object(service, 'require_auth', return_value=True):
            result = service.create_teams(new_team, 'valid_token')
        
        assert 'Inserted' in str(result)
        
        # Verify team was added
        team = repo.get_by_id(6)  # Next ID
        assert team is not None
        assert team['abbrev'] == 'CLE2'
    
    def test_create_multiple_teams(self, repo, cache):
        """Create multiple new teams."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        new_teams = [
            {'abbrev': 'TST1', 'sname': 'Test1', 'lname': 'Test Team 1', 'gmid': 100, 'season': 10},
            {'abbrev': 'TST2', 'sname': 'Test2', 'lname': 'Test Team 2', 'gmid': 101, 'season': 10},
        ]
        
        with patch.object(service, 'require_auth', return_value=True):
            result = service.create_teams(new_teams, 'valid_token')
        
        assert 'Inserted 2 teams' in str(result)
    
    def test_create_duplicate_fails(self, repo, cache):
        """Creating duplicate team should fail."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        duplicate = [{'abbrev': 'BAL', 'sname': 'Dup', 'lname': 'Duplicate', 'gmid': 999, 'season': 10}]
        
        with patch.object(service, 'require_auth', return_value=True):
            with pytest.raises(Exception) as exc_info:
                service.create_teams(duplicate, 'valid_token')
        
        assert 'already exists' in str(exc_info.value)
    
    def test_create_requires_auth(self, repo, cache):
        """Creating teams requires authentication."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        new_team = [{'abbrev': 'TST', 'sname': 'Test', 'lname': 'Test', 'gmid': 999, 'season': 10}]
        
        with pytest.raises(Exception) as exc_info:
            service.create_teams(new_team, 'bad_token')
        
        assert exc_info.value.status_code == 401


class TestTeamServiceDelete:
    """Tests for delete_team method."""
    
    def test_delete_team(self, repo, cache):
        """Delete existing team."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        # Verify team exists
        assert repo.get_by_id(1) is not None
        
        with patch.object(service, 'require_auth', return_value=True):
            result = service.delete_team(1, 'valid_token')
        
        assert 'deleted' in str(result)
        
        # Verify team is gone
        assert repo.get_by_id(1) is None
    
    def test_delete_nonexistent_team(self, repo, cache):
        """Delete fails for non-existent team."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        with patch.object(service, 'require_auth', return_value=True):
            with pytest.raises(Exception) as exc_info:
                service.delete_team(99999, 'valid_token')
        
        assert 'not found' in str(exc_info.value)
    
    def test_delete_requires_auth(self, repo, cache):
        """Deleting requires authentication."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        with pytest.raises(Exception) as exc_info:
            service.delete_team(1, 'bad_token')
        
        assert exc_info.value.status_code == 401


class TestTeamServiceCache:
    """Tests for cache functionality."""
    
    def test_cache_set_on_read(self, service, cache):
        """Cache is set on team read."""
        service.get_teams(season=10)
        
        assert cache.was_called('set')
    
    def test_cache_invalidation_on_update(self, repo, cache):
        """Cache is invalidated on team update."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        # Read to set cache
        service.get_teams(season=10)
        
        # Update should invalidate cache
        with patch.object(service, 'require_auth', return_value=True):
            service.update_team(1, {'abbrev': 'TEST'}, 'valid_token')
        
        # Should have invalidate/delete calls
        delete_calls = [c for c in cache.get_calls() if c.get('method') == 'delete']
        assert len(delete_calls) > 0
    
    def test_cache_invalidation_on_create(self, repo, cache):
        """Cache is invalidated on team create."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        # Set up some cache data
        cache.set('test:key', 'value', 300)
        
        with patch.object(service, 'require_auth', return_value=True):
            service.create_teams([{
                'abbrev': 'NEW',
                'sname': 'New',
                'lname': 'New Team',
                'gmid': 888,
                'season': 10
            }], 'valid_token')
        
        # Should have invalidate calls
        assert len(cache.get_calls()) > 0
    
    def test_cache_invalidation_on_delete(self, repo, cache):
        """Cache is invalidated on team delete."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        cache.set('test:key', 'value', 300)
        
        with patch.object(service, 'require_auth', return_value=True):
            service.delete_team(1, 'valid_token')
        
        assert len(cache.get_calls()) > 0


class TestTeamServiceValidation:
    """Tests for input validation and edge cases."""
    
    def test_invalid_season_returns_empty(self, service):
        """Invalid season returns empty result."""
        result = service.get_teams(season=999)
        
        assert result['count'] == 0 or result['teams'] == []
    
    def test_sort_with_no_results(self, service):
        """Sorting with no results doesn't error."""
        result = service.get_teams(season=999, active_only=True)
        
        assert result['count'] == 0 or result['teams'] == []
    
    def test_filter_nonexistent_abbrev(self, service):
        """Filter by non-existent abbreviation."""
        result = service.get_teams(season=10, team_abbrev=['XYZ'])
        
        assert result['count'] == 0


class TestTeamServiceIntegration:
    """Integration tests combining multiple operations."""
    
    def test_full_crud_cycle(self, repo, cache):
        """Test complete CRUD cycle."""
        config = ServiceConfig(team_repo=repo, cache=cache)
        service = TeamService(config=config)
        
        # CREATE
        with patch.object(service, 'require_auth', return_value=True):
            create_result = service.create_teams([{
                'abbrev': 'CRUD',
                'sname': 'Test',
                'lname': 'CRUD Test Team',
                'gmid': 777,
                'season': 10
            }], 'valid_token')
        
        # READ
        search_result = service.get_teams(season=10, team_abbrev=['CRUD'])
        assert search_result['count'] >= 1
        
        team_id = search_result['teams'][0].get('id')
        
        # UPDATE
        with patch.object(service, 'require_auth', return_value=True):
            update_result = service.update_team(team_id, {'sname': 'Updated'}, 'valid_token')
        assert update_result.get('sname') == 'Updated'
        
        # DELETE
        with patch.object(service, 'require_auth', return_value=True):
            delete_result = service.delete_team(team_id, 'valid_token')
        assert 'deleted' in str(delete_result)
        
        # VERIFY DELETED
        get_result = service.get_team(team_id)
        assert get_result is None
    
    def test_filter_then_get(self, service):
        """Filter teams then get individual team."""
        # First filter
        filtered = service.get_teams(season=10, team_abbrev=['BAL'])
        assert filtered['count'] >= 1
        
        # Then get by ID
        team_id = filtered['teams'][0].get('id')
        single = service.get_team(team_id)
        
        assert single is not None
        assert single.get('abbrev') == 'BAL'


# ============================================================================
# RUN TESTS
# ============================================================================

if __name__ == "__main__":
    pytest.main([__file__, "-v", "--tb=short"])
