from abc import ABC, abstractmethod
from infer_db.utils.logger_util import logger
from .base import FKStrategy
from infer_db.connectors import BaseConnector

class ExistingConstraintFKStrategy(FKStrategy):
    def detect(self, source_table: str, target_table: str, source_cols: list, target_pks: dict, connector: BaseConnector, source_pks_list: list):
        explicit_fks = connector.get_existing_fks(source_table)
        
        candidates = []
        for fk in explicit_fks:
            # Filter: only return FKs that point to the current 'target_table'
            if fk['target_table'] == target_table:
                candidates.append({
                    'source_table': source_table,
                    'source_col': fk['source_col'],
                    'target_table': target_table,
                    'target_col': fk['target_col'],
                    'strategy': 'ExistingConstraint',
                    'confidence': 1.0  # Absolute certainty
                })
        return candidates
    
class NamingConventionFKStrategy(FKStrategy):
    def detect(self, source_table: str, target_table: str, source_cols: list, target_pks: dict, connector: BaseConnector, source_pks_list=None):
        candidates = []
        
        for target_pk_col, pk_info in target_pks.items():
            
            # 1. Generate Table Name Variations (users -> user)
            target_variations = [target_table]
            # RULE: Handle "ies" -> "y" (categories -> category)
            if target_table.endswith('ies'):
                target_variations.append(target_table[:-3] + 'y') 
            # RULE: Handle standard "s" (users -> user)
            elif target_table.endswith('s'):
                target_variations.append(target_table[:-1])
           
            
            expected_names = set()
            
            for var in target_variations:
                # PATTERN A: Strict Composite (Table_PK)
                # Matches: user_id, category_name
                expected_names.add(f"{var}_{target_pk_col}")
                expected_names.add(f"{var}{target_pk_col}")
                
                # PATTERN B: Surrogate ID Fallback
                # If target is a Surrogate (e.g. 'uuid'), we also accept 'table_id'
                if pk_info.get('is_surrogate', False):
                    expected_names.add(f"{var}_id")
                    expected_names.add(f"{var}id")

            # Lowercase for comparison
            expected_names = {n.lower() for n in expected_names}

            # 2. Check Source Columns
            for col in source_cols:
                col_name = col['column'].lower()
                source_type = col['dtype']
                
                # STRICT MATCH ONLY: The column name must BE one of the expected names.
                # No 'endswith', no 'contains', no 'exact match of PK alone'.
                if col_name in expected_names:
                    
                    # 3. Type Check (Safety Net)
                    target_type = pk_info.get('dtype', 'unknown')
                    if not self.check_types_compatible(source_type, target_type):
                        continue 

                    candidates.append({
                        'source_table': source_table,
                        'source_col': col['column'],
                        'target_table': target_table,
                        'target_col': target_pk_col,
                        'strategy': 'NamingConvention',
                        'confidence': 'High'
                    })
        
        return candidates
    
class ContainmentFKStrategy(FKStrategy):
    def detect(self, source_table: str, target_table: str, source_cols: list, target_pks: dict, connector: BaseConnector, source_pks_list: list):
        candidates = []
        
        # We assume we are validating candidates found by other strategies (like NamingConvention)
        # OR we can brute-force scan "compatible" columns. 
        # For efficiency, let's look at pairs that match Types.
        
        for target_pk_col, pk_info in target_pks.items():
            
            # USER CONSTRAINT: Only run this check if the Target PK is NOT a surrogate
            # (Natural Key Validation)
            if pk_info.get('is_surrogate', False):
                continue

            for col in source_cols:
                source_col = col['column']
                # 1. Skip if Source Column is ITSELF a Primary Key
                if source_col in source_pks_list:
                    continue

                # 2. Type Check (Fastest Filter)
                if not self.check_types_compatible(col['dtype'], pk_info.get('dtype', '')):
                    continue

                # 3. The Containment Check (The heavy lifter)
                # "Are there any orphan values in source_col?"
                if connector.check_containment(source_table, source_col, target_table, target_pk_col):
                    candidates.append({
                        'source_table': source_table,
                        'source_col': source_col,
                        'target_table': target_table,
                        'target_col': target_pk_col,
                        'strategy': 'ValueContainment',
                        'confidence': 'High'
                    })
        return candidates