from typing import List, Dict, Optional
from datetime import datetime, date
from django.utils import timezone
from django.db.models import Q
from stamps.models import Stamp
from payroll.models import PayrollIntegration, PayrollSubmission, PayrollSubmissionStamp
from payroll.adapters.factory import PayrollAdapterFactory
from payroll.services.mapping_service import MappingService


class PayrollService:
    """Service for handling payroll submissions"""
    
    def __init__(self, payroll_integration: PayrollIntegration):
        """
        Initialize service with payroll integration
        
        Args:
            payroll_integration: PayrollIntegration instance
        """
        self.integration = payroll_integration
        self.adapter = PayrollAdapterFactory.create_adapter(
            payroll_integration.provider,
            {
                'api_endpoint': payroll_integration.api_endpoint or '',
                'api_key': payroll_integration.api_key or '',
                'api_secret': payroll_integration.api_secret or ''
            }
        )
        self.mapping_service = MappingService()
    
    def submit_timesheet(
        self,
        user_id: str,
        period_start: date,
        period_end: date,
        stamp_ids: Optional[List[str]] = None
    ) -> PayrollSubmission:
        """
        Submit timesheet for a user for a given period
        
        Args:
            user_id: Internal user UUID
            period_start: Start date of period
            period_end: End date of period
            stamp_ids: Optional list of specific stamp IDs to include
            
        Returns:
            PayrollSubmission instance
        """
        if not self.adapter:
            raise ValueError(f"Unsupported payroll provider: {self.integration.provider}")
        
        # Get employee mapping
        employee_id = self.mapping_service.get_employee_mapping(user_id, self.integration)
        if not employee_id:
            raise ValueError(f"No employee mapping found for user {user_id}")
        
        # Get stamps for the period
        query = Q(
            user_id=user_id,
            date__gte=period_start,
            date__lte=period_end
        )
        
        if stamp_ids:
            query &= Q(id__in=stamp_ids)
        
        stamps = Stamp.objects.filter(query).order_by('date', 'time')
        
        if not stamps.exists():
            raise ValueError("No stamps found for the specified period")
        
        # Transform stamp data
        timesheet_data = self._transform_stamp_data(stamps, employee_id)
        
        # Submit to payroll system
        period_start_dt = datetime.combine(period_start, datetime.min.time())
        period_end_dt = datetime.combine(period_end, datetime.max.time())
        
        result = self.adapter.submit_timesheet(
            employee_id,
            period_start_dt,
            period_end_dt,
            timesheet_data
        )
        
        # Create submission record
        submission = PayrollSubmission.objects.create(
            payroll_integration=self.integration,
            user_id=user_id,
            period_start=period_start,
            period_end=period_end,
            submission_status='submitted' if result['success'] else 'failed',
            external_reference_id=result.get('reference_id', ''),
            submitted_at=timezone.now() if result['success'] else None,
            error_message='\n'.join(result.get('errors', [])) if not result['success'] else ''
        )
        
        # Link stamps to submission
        for stamp in stamps:
            PayrollSubmissionStamp.objects.create(
                submission=submission,
                stamp=stamp
            )
        
        # Update last sync
        if result['success']:
            self.integration.last_sync = timezone.now()
            self.integration.save(update_fields=['last_sync'])
        
        return submission
    
    def _transform_stamp_data(self, stamps: List[Stamp], employee_id: str) -> Dict:
        """
        Transform stamps using adapter, with proper mapping lookups
        """
        # Create a wrapper adapter that has access to mapping service
        class MappedAdapter:
            def __init__(self, adapter, mapping_service, integration):
                self.adapter = adapter
                self.mapping_service = mapping_service
                self.integration = integration
            
            def get_project_mapping(self, project_id: str) -> Optional[str]:
                return self.mapping_service.get_project_mapping(project_id, self.integration)
        
        # Temporarily inject mapping methods into adapter
        original_get_project = self.adapter.get_project_mapping
        original_get_employee = self.adapter.get_employee_mapping
        
        def get_project_mapping(project_id: str) -> Optional[str]:
            return self.mapping_service.get_project_mapping(str(project_id), self.integration)
        
        def get_employee_mapping(user_id: str) -> Optional[str]:
            return self.mapping_service.get_employee_mapping(user_id, self.integration)
        
        self.adapter.get_project_mapping = get_project_mapping
        self.adapter.get_employee_mapping = get_employee_mapping
        
        try:
            result = self.adapter.transform_stamp_data(list(stamps), employee_id)
        finally:
            # Restore original methods
            self.adapter.get_project_mapping = original_get_project
            self.adapter.get_employee_mapping = original_get_employee
        
        return result
    
    def validate_connection(self) -> Dict:
        """
        Validate connection to payroll system
        
        Returns:
            Dictionary with validation result
        """
        if not self.adapter:
            return {
                'valid': False,
                'message': f'Unsupported provider: {self.integration.provider}'
            }
        
        is_valid = self.adapter.validate_credentials()
        return {
            'valid': is_valid,
            'message': 'Connection successful' if is_valid else 'Connection failed'
        }

