"""
Relationship Service

This module establishes relationships between collections after import.
It links people to participants, participants to competitions, and results to rounds/shots.

Key responsibilities:
- Link people records to participant records by code
- Link participants to competitions
- Generate rounds and shots from results data
- Validate referential integrity
"""

from typing import Dict, Any, List, Optional, Tuple
from bson import ObjectId
from datetime import datetime


class RelationshipService:
    """
    Service for establishing and managing relationships between collections.
    
    This service creates links between:
    - people ↔ participants (by code)
    - participants ↔ competitions (by competition_code)
    - results → rounds → shots (hierarchical)
    """
    
    def __init__(self, mongo_service):
        """
        Initialize relationship service.
        
        Args:
            mongo_service: MongoService instance
        """
        self.mongo = mongo_service
    
    # =========================================================================
    # PEOPLE ↔ PARTICIPANTS LINKING
    # =========================================================================
    
    def link_people_to_participants(
        self,
        competition_code: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Link people records to participant records by matching codes.
        
        This creates a reference from participants to people collection,
        allowing navigation from participant → person.
        
        Args:
            competition_code: Optional filter by competition
            
        Returns:
            Dictionary with linking results
        """
        people_collection = self.mongo.db['people']
        participants_collection = self.mongo.db['participants']
        
        # Build index of people by code
        people_by_code = {}
        for person in people_collection.find({}, {'_id': 1, 'code': 1, 'codes': 1}):
            # Index by primary code
            if person.get('code'):
                people_by_code[str(person['code'])] = person['_id']
            # Index by all codes in codes array
            for code in person.get('codes', []):
                people_by_code[str(code)] = person['_id']
        
        # Find participants to link
        query = {}
        if competition_code:
            query['odf_body.competition_code'] = competition_code
        
        linked_count = 0
        not_found_count = 0
        already_linked_count = 0
        not_found_codes = []
        
        for participant in participants_collection.find(query):
            participant_code = participant.get('code')
            
            if not participant_code:
                continue
            
            # Check if already linked
            if participant.get('person_id'):
                already_linked_count += 1
                continue
            
            # Find matching person
            person_id = people_by_code.get(str(participant_code))
            
            if person_id:
                # Create link
                participants_collection.update_one(
                    {'_id': participant['_id']},
                    {
                        '$set': {
                            'person_id': person_id,
                            'linked_at': datetime.utcnow().isoformat()
                        }
                    }
                )
                linked_count += 1
            else:
                not_found_count += 1
                if participant_code not in not_found_codes:
                    not_found_codes.append(participant_code)
        
        return {
            'success': True,
            'linked_count': linked_count,
            'already_linked_count': already_linked_count,
            'not_found_count': not_found_count,
            'not_found_codes': not_found_codes[:20],  # Limit to first 20
            'people_index_size': len(people_by_code)
        }
    
    # =========================================================================
    # ROUNDS GENERATION
    # =========================================================================
    
    def generate_rounds_from_results(
        self,
        competition_code: str
    ) -> Dict[str, Any]:
        """
        Generate rounds collection from results data.
        
        Extracts unique rounds from odf_results and creates
        separate round documents for easier querying.
        
        Args:
            competition_code: Competition code to process
            
        Returns:
            Dictionary with generation results
        """
        results_collection = self.mongo.db['odf_results']
        rounds_collection = self.mongo.db['rounds']
        
        # Find all results for this competition
        results = results_collection.find({
            'odf_body.competition_code': competition_code
        })
        
        # Extract unique rounds
        rounds_data = {}
        
        for result in results:
            odf_body = result.get('odf_body', {})
            
            # Extract round info
            round_code = odf_body.get('round')
            event_code = odf_body.get('event_code')
            
            if not round_code:
                continue
            
            round_key = f"{competition_code}_{event_code}_{round_code}"
            
            if round_key not in rounds_data:
                rounds_data[round_key] = {
                    'competition_code': competition_code,
                    'event_code': event_code,
                    'round_code': round_code,
                    'round_name': self._get_round_name(round_code),
                    'result_ids': [],
                    'participant_count': 0,
                    'created_at': datetime.utcnow().isoformat()
                }
            
            rounds_data[round_key]['result_ids'].append(result['_id'])
            rounds_data[round_key]['participant_count'] += 1
        
        # Insert rounds
        inserted_count = 0
        updated_count = 0
        
        for round_key, round_doc in rounds_data.items():
            # Check if round already exists
            existing = rounds_collection.find_one({
                'competition_code': round_doc['competition_code'],
                'event_code': round_doc['event_code'],
                'round_code': round_doc['round_code']
            })
            
            if existing:
                # Update existing round
                rounds_collection.update_one(
                    {'_id': existing['_id']},
                    {'$set': {
                        'result_ids': round_doc['result_ids'],
                        'participant_count': round_doc['participant_count'],
                        'updated_at': datetime.utcnow().isoformat()
                    }}
                )
                updated_count += 1
            else:
                # Insert new round
                rounds_collection.insert_one(round_doc)
                inserted_count += 1
        
        return {
            'success': True,
            'inserted_count': inserted_count,
            'updated_count': updated_count,
            'total_rounds': len(rounds_data)
        }
    
    def _get_round_name(self, round_code: str) -> str:
        """
        Convert round code to human-readable name.
        
        Args:
            round_code: Round code (e.g., 'R1', 'QF', 'SF', 'F')
            
        Returns:
            Human-readable round name
        """
        round_names = {
            'R1': 'Round 1',
            'R2': 'Round 2',
            'R3': 'Round 3',
            'R4': 'Round 4',
            'QF': 'Quarterfinal',
            'SF': 'Semifinal',
            'F': 'Final',
            'BM': 'Bronze Medal Match',
            'GM': 'Gold Medal Match'
        }
        return round_names.get(round_code, round_code)
    
    # =========================================================================
    # SHOTS GENERATION
    # =========================================================================
    
    def generate_shots_from_play_by_play(
        self,
        competition_code: str
    ) -> Dict[str, Any]:
        """
        Generate shots collection from play-by-play data.
        
        Extracts individual shots from odf_play_by_play and creates
        separate shot documents for detailed analysis.
        
        Args:
            competition_code: Competition code to process
            
        Returns:
            Dictionary with generation results
        """
        play_by_play_collection = self.mongo.db['odf_play_by_play']
        shots_collection = self.mongo.db['shots']
        
        # Find all play-by-play data for this competition
        pbp_data = play_by_play_collection.find({
            'odf_body.competition_code': competition_code
        })
        
        shots_to_insert = []
        
        for pbp in pbp_data:
            odf_body = pbp.get('odf_body', {})
            
            # Extract shot data (structure depends on ODF format)
            # This is a template - adjust based on actual ODF structure
            competitor_code = pbp.get('competitor', {}).get('code')
            event_code = odf_body.get('event_code')
            round_code = odf_body.get('round')
            
            # Create shot document
            shot_doc = {
                'competition_code': competition_code,
                'event_code': event_code,
                'round_code': round_code,
                'competitor_code': competitor_code,
                'play_by_play_id': pbp['_id'],
                'period': odf_body.get('period'),
                'time': odf_body.get('time'),
                'created_at': datetime.utcnow().isoformat()
            }
            
            shots_to_insert.append(shot_doc)
        
        # Bulk insert shots
        inserted_count = 0
        if shots_to_insert:
            result = shots_collection.insert_many(shots_to_insert)
            inserted_count = len(result.inserted_ids)
        
        return {
            'success': True,
            'inserted_count': inserted_count
        }
    
    # =========================================================================
    # RELATIONSHIP VALIDATION
    # =========================================================================
    
    def validate_relationships(
        self,
        collection_name: str
    ) -> Dict[str, Any]:
        """
        Validate referential integrity for a collection.
        
        Checks that all foreign key references point to existing records.
        
        Args:
            collection_name: Collection to validate
            
        Returns:
            Dictionary with validation results
        """
        # Define relationship rules
        relationship_rules = {
            'participants': [
                {'field': 'person_id', 'references': 'people', 'ref_field': '_id'}
            ],
            'odf_results': [
                {'field': 'competitor.code', 'references': 'participants', 'ref_field': 'code'}
            ],
            'rounds': [
                {'field': 'competition_code', 'references': 'competitions', 'ref_field': 'code'}
            ]
        }
        
        rules = relationship_rules.get(collection_name, [])
        
        if not rules:
            return {
                'success': True,
                'message': f'No relationship rules defined for {collection_name}',
                'valid_count': 0,
                'invalid_count': 0
            }
        
        collection = self.mongo.db[collection_name]
        
        valid_count = 0
        invalid_count = 0
        invalid_records = []
        
        for doc in collection.find():
            is_valid = True
            
            for rule in rules:
                field = rule['field']
                ref_collection = rule['references']
                ref_field = rule['ref_field']
                
                # Get value from document (handle nested fields)
                value = self._get_nested_value(doc, field)
                
                if value is None:
                    continue  # Null references are allowed
                
                # Check if reference exists
                ref_coll = self.mongo.db[ref_collection]
                
                if ref_field == '_id' and isinstance(value, str):
                    try:
                        value = ObjectId(value)
                    except:
                        pass
                
                exists = ref_coll.find_one({ref_field: value})
                
                if not exists:
                    is_valid = False
                    invalid_records.append({
                        'doc_id': str(doc['_id']),
                        'field': field,
                        'value': str(value),
                        'missing_in': ref_collection
                    })
            
            if is_valid:
                valid_count += 1
            else:
                invalid_count += 1
        
        return {
            'success': True,
            'valid_count': valid_count,
            'invalid_count': invalid_count,
            'invalid_records': invalid_records[:50]  # Limit output
        }
    
    def _get_nested_value(self, doc: Dict[str, Any], field_path: str) -> Any:
        """
        Get value from nested dictionary using dot notation.
        
        Args:
            doc: Document dictionary
            field_path: Field path (e.g., 'competitor.code')
            
        Returns:
            Value at path or None
        """
        keys = field_path.split('.')
        value = doc
        
        for key in keys:
            if isinstance(value, dict) and key in value:
                value = value[key]
            else:
                return None
        
        return value
    
    # =========================================================================
    # STATISTICS
    # =========================================================================
    
    def get_relationship_statistics(self) -> Dict[str, Any]:
        """
        Get statistics about relationships across collections.
        
        Returns:
            Dictionary with relationship statistics
        """
        stats = {}
        
        # People with codes
        people_coll = self.mongo.db['people']
        stats['people'] = {
            'total': people_coll.count_documents({}),
            'with_code': people_coll.count_documents({'code': {'$exists': True, '$ne': None}}),
            'with_codes_array': people_coll.count_documents({'codes': {'$exists': True, '$ne': []}})
        }
        
        # Participants linked to people
        participants_coll = self.mongo.db['participants']
        stats['participants'] = {
            'total': participants_coll.count_documents({}),
            'linked_to_people': participants_coll.count_documents({'person_id': {'$exists': True}})
        }
        
        # Rounds
        if 'rounds' in self.mongo.db.list_collection_names():
            rounds_coll = self.mongo.db['rounds']
            stats['rounds'] = {
                'total': rounds_coll.count_documents({})
            }
        
        # Shots
        if 'shots' in self.mongo.db.list_collection_names():
            shots_coll = self.mongo.db['shots']
            stats['shots'] = {
                'total': shots_coll.count_documents({})
            }
        
        return stats
