"""Speaker profile repository for v2 speaker diarization features.

Provides data access layer for speaker profile operations including CRUD operations,
speaker identification, and embedding management.
"""

from datetime import datetime
from typing import List, Optional, Dict, Any, Protocol
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_

from src.database.models import SpeakerProfile


class SpeakerProfileRepositoryProtocol(Protocol):
    """Protocol for speaker profile repository operations."""
    
    def create(self, name: str, user_id: Optional[int] = None, 
               characteristics: Optional[Dict] = None, 
               embedding: Optional[str] = None) -> SpeakerProfile:
        """Create a new speaker profile."""
        ...
    
    def get_by_id(self, profile_id: int) -> Optional[SpeakerProfile]:
        """Get speaker profile by ID."""
        ...
    
    def get_by_user(self, user_id: int) -> List[SpeakerProfile]:
        """Get all speaker profiles for a user."""
        ...
    
    def get_by_name(self, name: str) -> List[SpeakerProfile]:
        """Get speaker profiles by name."""
        ...
    
    def update(self, profile_id: int, **kwargs) -> Optional[SpeakerProfile]:
        """Update a speaker profile."""
        ...
    
    def delete(self, profile_id: int) -> bool:
        """Delete a speaker profile."""
        ...
    
    def increment_sample_count(self, profile_id: int) -> Optional[SpeakerProfile]:
        """Increment the sample count for a speaker profile."""
        ...
    
    def search_by_characteristics(self, characteristics: Dict) -> List[SpeakerProfile]:
        """Search speaker profiles by characteristics."""
        ...


class SpeakerProfileRepository:
    """Repository for speaker profile operations.
    
    Provides clean data access layer for speaker profile management
    including CRUD operations and speaker identification features.
    """
    
    def __init__(self, session: Session):
        """Initialize repository with database session."""
        self.session = session
    
    def create(self, name: str, user_id: Optional[int] = None, 
               characteristics: Optional[Dict] = None, 
               embedding: Optional[str] = None) -> SpeakerProfile:
        """Create a new speaker profile.
        
        Args:
            name: Speaker name
            user_id: Associated user ID (optional)
            characteristics: Voice characteristics (optional)
            embedding: Speaker embedding data (optional)
            
        Returns:
            Created speaker profile
        """
        profile = SpeakerProfile(
            name=name,
            user_id=user_id,
            characteristics=characteristics or {},
            embedding=embedding,
            sample_count=0
        )
        
        self.session.add(profile)
        self.session.commit()
        self.session.refresh(profile)
        
        return profile
    
    def get_by_id(self, profile_id: int) -> Optional[SpeakerProfile]:
        """Get speaker profile by ID.
        
        Args:
            profile_id: Speaker profile ID
            
        Returns:
            Speaker profile or None if not found
        """
        return self.session.query(SpeakerProfile).filter(
            SpeakerProfile.id == profile_id
        ).first()
    
    def get_by_user(self, user_id: int) -> List[SpeakerProfile]:
        """Get all speaker profiles for a user.
        
        Args:
            user_id: User ID
            
        Returns:
            List of speaker profiles
        """
        return self.session.query(SpeakerProfile).filter(
            SpeakerProfile.user_id == user_id
        ).all()
    
    def get_by_name(self, name: str) -> List[SpeakerProfile]:
        """Get speaker profiles by name.
        
        Args:
            name: Speaker name (partial match)
            
        Returns:
            List of matching speaker profiles
        """
        return self.session.query(SpeakerProfile).filter(
            SpeakerProfile.name.ilike(f"%{name}%")
        ).all()
    
    def update(self, profile_id: int, **kwargs) -> Optional[SpeakerProfile]:
        """Update a speaker profile.
        
        Args:
            profile_id: Speaker profile ID
            **kwargs: Fields to update
            
        Returns:
            Updated speaker profile or None if not found
        """
        profile = self.get_by_id(profile_id)
        if not profile:
            return None
        
        # Update allowed fields
        allowed_fields = ['name', 'characteristics', 'embedding', 'sample_count', 'user_id']
        
        for key, value in kwargs.items():
            if key in allowed_fields and hasattr(profile, key):
                setattr(profile, key, value)
        
        # Update timestamp
        profile.updated_at = datetime.utcnow()
        
        self.session.commit()
        self.session.refresh(profile)
        
        return profile
    
    def delete(self, profile_id: int) -> bool:
        """Delete a speaker profile.
        
        Args:
            profile_id: Speaker profile ID
            
        Returns:
            True if deleted, False if not found
        """
        profile = self.get_by_id(profile_id)
        if not profile:
            return False
        
        self.session.delete(profile)
        self.session.commit()
        
        return True
    
    def increment_sample_count(self, profile_id: int) -> Optional[SpeakerProfile]:
        """Increment the sample count for a speaker profile.
        
        Args:
            profile_id: Speaker profile ID
            
        Returns:
            Updated speaker profile or None if not found
        """
        profile = self.get_by_id(profile_id)
        if not profile:
            return None
        
        profile.sample_count += 1
        profile.updated_at = datetime.utcnow()
        
        self.session.commit()
        self.session.refresh(profile)
        
        return profile
    
    def search_by_characteristics(self, characteristics: Dict) -> List[SpeakerProfile]:
        """Search speaker profiles by characteristics.
        
        Args:
            characteristics: Characteristics to search for
            
        Returns:
            List of matching speaker profiles
        """
        query = self.session.query(SpeakerProfile)
        
        # Build search conditions
        conditions = []
        for key, value in characteristics.items():
            # Search for characteristic in JSONB field
            condition = SpeakerProfile.characteristics.contains({key: value})
            conditions.append(condition)
        
        if conditions:
            # Use OR logic for multiple characteristics
            query = query.filter(or_(*conditions))
        
        return query.all()
    
    def get_all(self, limit: Optional[int] = None, offset: int = 0) -> List[SpeakerProfile]:
        """Get all speaker profiles with optional pagination.
        
        Args:
            limit: Maximum number of profiles to return
            offset: Number of profiles to skip
            
        Returns:
            List of speaker profiles
        """
        query = self.session.query(SpeakerProfile).order_by(SpeakerProfile.created_at.desc())
        
        if offset > 0:
            query = query.offset(offset)
        
        if limit:
            query = query.limit(limit)
        
        return query.all()
    
    def count_by_user(self, user_id: int) -> int:
        """Count speaker profiles for a user.
        
        Args:
            user_id: User ID
            
        Returns:
            Number of speaker profiles
        """
        return self.session.query(SpeakerProfile).filter(
            SpeakerProfile.user_id == user_id
        ).count()
    
    def get_most_active_speakers(self, limit: int = 10) -> List[SpeakerProfile]:
        """Get speakers with the highest sample counts.
        
        Args:
            limit: Maximum number of speakers to return
            
        Returns:
            List of speaker profiles ordered by sample count
        """
        return self.session.query(SpeakerProfile).order_by(
            SpeakerProfile.sample_count.desc()
        ).limit(limit).all()
