"""Complete pipeline for oligo codon counting"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_pipeline.ipynb.

# %% auto 0
__all__ = ['PipelineConfig', 'ProgressReporter', 'OligoCodonPipeline']

# %% ../nbs/03_pipeline.ipynb 4
import os
import time
import logging
from pathlib import Path
from typing import List, Dict, Optional, Callable, Union, Tuple, Set
from dataclasses import dataclass

from .core import OligoCounter, OligoLoader
from .fastq import FastqHandler, OligoCodonProcessor
from .output import ResultsFormatter, ResultsSaver

# %% ../nbs/03_pipeline.ipynb 6
@dataclass
class PipelineConfig:
    """Configuration settings for oligo codon counting pipeline."""
    # Required parameters
    #fastq_path: str                  # Path to directory containing FASTQ files
    fastq_1: str = "../test_files/test_1.fq.gz"   
    fastq_2: str = "../test_files/test_2.fq.gz"   
    
    # Oligo configuration (one of these must be provided)
    oligos_file: Optional[str] = None       # Path to file with oligo sequences
    oligos_string: Optional[str] = None     # Comma-separated string of oligos
    oligos_list: Optional[List[str]] = None # List of oligo sequences
    
    # Output configuration
    output_path: str = "../test_files/test_outs"    # Path to save results
    output_prefix: str = ""           # Prefix for output files
    offset_oligo: int = 1             # Value to add to oligo index in output
    
    # Optional configuration
    log_file: Optional[str] = None    # Path to log file (if None, logs to console)
    log_level: int = logging.INFO     # Logging level
    
    def validate(self) -> None:
        """Validate configuration settings.
        
        Raises:
            ValueError: If any settings are invalid
        """
        # Check fastq_path exists
        if not os.path.exists(self.fastq_1):
            raise ValueError(f"FASTQ path does not exist: {self.fastq_1}")
        if not os.path.exists(self.fastq_2):
            raise ValueError(f"FASTQ path does not exist: {self.fastq_1}")
            
        
        # Check that at least one oligo source is provided
        oligo_sources = [self.oligos_file, self.oligos_string, self.oligos_list]
        if not any(oligo_sources):
            raise ValueError("No oligo source provided. Must specify one of: oligos_file, oligos_string, or oligos_list")
        
        # Check oligos file exists if provided
        if self.oligos_file and not os.path.exists(self.oligos_file):
            raise ValueError(f"Oligos file does not exist: {self.oligos_file}")
            
    def get_output_filename(self, extension: str = "csv") -> str:
        """Generate output filename based on configuration.
        
        Args:
            extension: File extension (default: 'csv')
            
        Returns:
            Output filename
        """
        if self.output_prefix:
            filename = f"{self.output_prefix}_counts"
        else:
            filename = f"counts"
        return f"{filename}.{extension}"

# %% ../nbs/03_pipeline.ipynb 8
#not used
class ProgressReporter:
    """Reports progress during long-running operations."""
    
    def __init__(self, logger: Optional[logging.Logger] = None):
        """Initialize the progress reporter.
        
        Args:
            logger: Logger instance to use for reporting
        """
        self.logger = logger or logging.getLogger(__name__)
        self.last_progress = 0
        self.last_time = time.time()
    
    def report(self, current: int, total: int, min_interval: float = 2.0, min_change: float = 1.0) -> None:
        """Report progress if enough time has passed or enough progress has been made.
        
        Args:
            current: Current progress value
            total: Total expected value
            min_interval: Minimum time interval between reports in seconds (default: 2.0)
            min_change: Minimum percentage change to trigger a new report (default: 1.0)
        """
        if total <= 0:
            return
            
        now = time.time()
        current_progress = (current / total) * 100
        
        # Check if we should report progress
        time_passed = now - self.last_time >= min_interval
        progress_made = abs(current_progress - self.last_progress) >= min_change
        
        if time_passed or progress_made or current >= total:
            self.logger.info(f"Progress: {current:,}/{total:,} reads processed ({current_progress:.1f}%)")
            self.last_progress = current_progress
            self.last_time = now

# %% ../nbs/03_pipeline.ipynb 10
class OligoCodonPipeline:
    """Complete pipeline for oligo codon counting."""
    
    def __init__(self, config: PipelineConfig):
        """Initialize the pipeline with configuration.
        
        Args:
            config: Pipeline configuration
        """
        self.config = config
        self.logger = self._setup_logging()
        self.oligos: List[str] = []
        self.reporter = ProgressReporter(self.logger)
    
    def _setup_logging(self) -> logging.Logger:
        """Set up logging based on configuration.
        
        Returns:
            Configured logger instance
        """
        logger = logging.getLogger("OligoCodonPipeline")
        logger.setLevel(self.config.log_level)
        
        # Clear existing handlers
        logger.handlers = []
        
        # Create formatter
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        
        # Always add console handler
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)
        logger.addHandler(console_handler)
        
        # Add file handler if specified
        if self.config.log_file:
            log_dir = os.path.dirname(self.config.log_file)
            if log_dir and not os.path.exists(log_dir):
                os.makedirs(log_dir)
                
            file_handler = logging.FileHandler(self.config.log_file)
            file_handler.setFormatter(formatter)
            logger.addHandler(file_handler)
            
        return logger
    
    def load_oligos(self) -> List[str]:
        """Load oligo sequences from the specified source.
        
        Returns:
            List of oligo sequences
            
        Raises:
            ValueError: If oligos cannot be loaded or are invalid
        """
        self.logger.info("Loading oligo sequences...")
        
        if self.config.oligos_file:
            self.logger.info(f"Loading oligos from file: {self.config.oligos_file}")
            oligos = OligoLoader.from_file(self.config.oligos_file)
        elif self.config.oligos_string:
            self.logger.info("Loading oligos from provided string")
            oligos = OligoLoader.from_string(self.config.oligos_string)
        elif self.config.oligos_list:
            self.logger.info("Using provided oligo list")
            oligos = OligoLoader.validate_oligos(self.config.oligos_list)
        else:
            raise ValueError("No oligo source specified")
            
        self.logger.info(f"Loaded {len(oligos)} oligo sequences")
        return oligos
    
    def find_fastq_files(self) -> Tuple[str, str]:
        """Find FASTQ files to process.
        
        Returns:
            Tuple of (read1_path, read2_path)
        """
        #self.logger.info(f"Finding FASTQ files in: {self.config.fastq_path}")
        #r1_path, r2_path = FastqHandler.find_fastq_pairs(self.config.fastq_path)
        #self.logger.info(f"Found FASTQ pair: {os.path.basename(r1_path)} and {os.path.basename(r2_path)}")
        return (self.config.fastq_1, self.config.fastq_2)
    
    def progress_callback(self, current: int, total: int) -> None:
        """Callback for reporting progress.
        
        Args:
            current: Current progress value
            total: Total expected value
        """
        self.reporter.report(current, total)
    
    def run(self) -> Dict:
        """Execute the complete pipeline.
        
        Returns:
            Dictionary with pipeline results and statistics
        """
        start_time = time.time()
        self.logger.info("Starting OligoCodonPipeline")
        
        # Validate configuration
        self.config.validate()
        
        # Load oligos
        self.oligos = self.load_oligos()
        
        # Find FASTQ files
        r1_path, r2_path = self.find_fastq_files()
        
        # Process FASTQ files
        self.logger.info("Processing FASTQ files...")
        processor = OligoCodonProcessor(self.oligos)
        results = processor.process_fastq_pair(r1_path, r2_path)
        
        # Format results
        self.logger.info("Formatting results...")
        result_df = ResultsFormatter.to_dataframe(results, self.oligos, self.config.offset_oligo)
        
        # Save results
        output_file = self.config.get_output_filename()
        self.logger.info(f"Saving results to: {os.path.join(self.config.output_path, output_file)}")
        saved_path = ResultsSaver.save_csv(result_df, self.config.output_path, output_file)
        
        # Generate summary
        summary = ResultsFormatter.summarize_results(result_df)
        
        
        # Calculate elapsed time
        end_time = time.time()
        elapsed_time = end_time - start_time
        self.logger.info(f"Pipeline completed in {elapsed_time:.2f} seconds")
        
        # Return results
        return {
            "csv_path": saved_path,
            #"excel_path": excel_path,
            #"json_path": json_path,
            "oligos_processed": len(self.oligos),
            "elapsed_time": elapsed_time,
            "summary": summary
        }
