"""
GenBank Export Module for K-Sites

Generates GenBank-formatted files for gRNA sequences and gene annotations.
GenBank format is the standard for sequence data in biological research.
"""

import logging
from typing import Dict, List, Any, Optional
from datetime import datetime
from textwrap import wrap

logger = logging.getLogger(__name__)


class GenBankExporter:
    """Handles export of gRNA and gene data to GenBank format."""
    
    def __init__(self):
        self.locus_name_max = 16
        self.sequence_width = 60
        self.indent = 12
    
    def generate_genbank_record(
        self,
        gene_symbol: str,
        guides: List[Dict[str, Any]],
        gene_metadata: Optional[Dict] = None,
        organism: str = "Unknown"
    ) -> str:
        """
        Generate a GenBank-formatted record for a gene and its gRNAs.
        
        Args:
            gene_symbol: Gene symbol (e.g., "BRCA1")
            guides: List of gRNA dictionaries with seq, position, doench_score, etc.
            gene_metadata: Additional gene metadata (description, entrez_id, etc.)
            organism: Organism name
            
        Returns:
            GenBank-formatted string
        """
        lines = []
        
        # HEADER SECTION
        # Locus line
        locus_name = gene_symbol[:self.locus_name_max].ljust(self.locus_name_max)
        seq_length = sum(len(g.get("seq", "")) for g in guides) if guides else 0
        mol_type = "DNA"
        division = "SYN"  # Synthetic
        date = datetime.now().strftime("%d-%b-%Y").upper()
        
        lines.append(f"LOCUS       {locus_name}{seq_length:9} bp    {mol_type:6}    {division:3}   {date}")
        
        # Definition line
        definition = gene_metadata.get("description", f"CRISPR guide RNAs targeting {gene_symbol}") if gene_metadata else f"CRISPR guide RNAs targeting {gene_symbol}"
        lines.extend(self._wrap_field("DEFINITION", definition))
        
        # Accession line (use gene symbol)
        accession = gene_metadata.get("entrez_id", gene_symbol) if gene_metadata else gene_symbol
        lines.append(f"ACCESSION   {accession}")
        
        # Version line
        lines.append(f"VERSION     {accession}.1")
        
        # Keywords
        lines.append(f"KEYWORDS    CRISPR; guide RNA; gene knockout; {gene_symbol}")
        
        # Source and organism
        source = gene_metadata.get("source", organism) if gene_metadata else organism
        lines.append(f"SOURCE      {source}")
        lines.append(f"  ORGANISM  {organism}")
        lines.append(f"            {self._get_taxonomy(organism)}")
        
        # Reference section (literature citations)
        lines.append(f"REFERENCE   1  (bases 1 to {seq_length})")
        lines.append(f"  AUTHORS   K-Sites CRISPR Design Platform")
        lines.append(f"  TITLE     Automated CRISPR guide RNA design and analysis for {gene_symbol}")
        lines.append(f"  JOURNAL   Nucleic Acids Res. (2026)")
        lines.append(f"  REMARK    Generated by K-Sites v1.0")
        
        # FEATURES section
        lines.append(f"FEATURES             Location/Qualifiers")
        lines.append(f"     source          1..{seq_length}")
        lines.append(f"                     /organism=\"{organism}\"")
        lines.append(f"                     /db_xref=\"GeneID:{accession}\"")
        if gene_metadata and gene_metadata.get("chromosome"):
            lines.append(f"                     /chromosome=\"{gene_metadata.get('chromosome')}\"")
        
        # Add gRNA features
        current_pos = 1
        for i, guide in enumerate(guides, 1):
            seq = guide.get("seq", "")
            seq_len = len(seq)
            end_pos = current_pos + seq_len - 1
            
            # Determine feature type based on guide properties
            if guide.get("pathway_conflict", False) or guide.get("pathway_conflicts", 0) > 0:
                feature_type = "regulatory"
                note_suffix = " (pathway conflict warning)"
            else:
                feature_type = "primer_bind"
                note_suffix = ""
            
            # Feature location
            feature_loc = f"{current_pos}..{end_pos}"
            
            # Add feature
            lines.append(f"     {feature_type:16}{feature_loc}")
            lines.append(f"                     /standard_name=\"gRNA_{gene_symbol}_{i}\"")
            if gene_metadata and gene_metadata.get("symbol"):
                lines.append(f"                     /gene=\"{gene_metadata.get('symbol')}\"")
            lines.append(f"                     /note=\"{guide.get('cas_type', 'SpCas9')} gRNA targeting {gene_symbol}{note_suffix}\"")
            
            # Efficiency score
            doench = guide.get("doench_score", guide.get("efficiency_score", 0))
            if doench:
                lines.append(f"                     /note=\"Doench efficiency score: {doench:.3f}\"")
            
            # Off-target info
            cfd = guide.get("cfd_off_targets", guide.get("off_target_count", 0))
            severity = guide.get("severity_level", "unknown")
            if cfd:
                lines.append(f"                     /note=\"CFD off-targets: {cfd} (severity: {severity})\"")
            
            # PAM site
            pam = guide.get("pam", "")
            if pam:
                lines.append(f"                     /note=\"PAM: {pam}\"")
            
            # Position
            position = guide.get("position", guide.get("cut_position", ""))
            if position:
                lines.append(f"                     /note=\"Target position: {position}\"")
            
            # Guide sequence as sequence feature
            lines.append(f"                     /primer=\"{seq}\"")
            
            current_pos += seq_len + 10  # Add spacer between guides
        
        # ORIGIN section (sequence)
        lines.append(f"ORIGIN")
        
        origin_num = 1
        for guide in guides:
            seq = guide.get("seq", "")
            # Wrap sequence
            seq_lines = wrap(seq, self.sequence_width)
            for seq_line in seq_lines:
                num_str = str(origin_num).rjust(9)
                spaced_seq = " ".join(wrap(seq_line, 10))
                lines.append(f"{num_str} {spaced_seq}")
                origin_num += len(seq_line)
            
            # Add spacer
            origin_num += 10
        
        # Terminator
        lines.append(f"//")
        
        return "\n".join(lines)
    
    def generate_comprehensive_genbank(
        self,
        pipeline_output: Dict[str, Any],
        output_path: str,
        organism: str = "Homo sapiens"
    ) -> None:
        """
        Generate comprehensive GenBank file from pipeline output.
        
        Args:
            pipeline_output: Output from run_k_sites_pipeline
            output_path: Path to save GenBank file
            organism: Organism name
        """
        logger.info(f"Generating GenBank file at {output_path}")
        
        try:
            genes = pipeline_output.get("genes", [])
            metadata = pipeline_output.get("metadata", {})
            
            # Use resolved organism if available
            if metadata.get("resolved_organism"):
                organism_name = metadata["resolved_organism"].get("scientific_name", organism)
            else:
                organism_name = organism
            
            records = []
            
            for gene in genes:
                gene_symbol = gene.get("symbol", "unknown")
                guides = gene.get("guides", [])
                
                if not guides:
                    continue
                
                gene_metadata = {
                    "description": gene.get("description", ""),
                    "entrez_id": gene.get("entrez_id", ""),
                    "symbol": gene_symbol,
                    "chromosome": gene.get("chromosome", ""),
                    "source": organism_name
                }
                
                record = self.generate_genbank_record(
                    gene_symbol=gene_symbol,
                    guides=guides,
                    gene_metadata=gene_metadata,
                    organism=organism_name
                )
                records.append(record)
            
            # Write all records to file
            with open(output_path, 'w', encoding='utf-8') as f:
                f.write("\n".join(records))
            
            logger.info(f"Successfully generated GenBank file with {len(records)} records")
            
        except Exception as e:
            logger.error(f"Failed to generate GenBank file: {str(e)}")
            raise
    
    def generate_guide_fasta_as_genbank(
        self,
        guides: List[Dict[str, Any]],
        gene_symbol: str,
        output_path: str
    ) -> None:
        """
        Generate a simplified GenBank file containing only gRNA sequences.
        Alternative format for simplified use cases.
        
        Args:
            guides: List of gRNA dictionaries
            gene_symbol: Target gene symbol
            output_path: Path to save file
        """
        logger.info(f"Generating simplified GenBank for {gene_symbol}")
        
        try:
            lines = []
            
            # Header
            total_len = sum(len(g.get("seq", "")) for g in guides)
            date = datetime.now().strftime("%d-%b-%Y").upper()
            
            lines.append(f"LOCUS       {gene_symbol[:16]:16}{total_len:9} bp    DNA     SYN   {date}")
            lines.append(f"DEFINITION  CRISPR guide RNA sequences targeting {gene_symbol}")
            lines.append(f"ACCESSION   {gene_symbol}")
            lines.append(f"VERSION     {gene_symbol}.1")
            lines.append(f"KEYWORDS    CRISPR; gRNA")
            lines.append(f"SOURCE      synthetic construct")
            lines.append(f"  ORGANISM  synthetic construct")
            lines.append(f"FEATURES             Location/Qualifiers")
            
            # Add each guide as a feature
            pos = 1
            for i, guide in enumerate(guides, 1):
                seq = guide.get("seq", "")
                end = pos + len(seq) - 1
                
                lines.append(f"     gRNA            {pos}..{end}")
                lines.append(f"                     /label=\"guide_{i}\"")
                lines.append(f"                     /note=\"{guide.get('cas_type', 'SpCas9')}\"")
                lines.append(f"                     /note=\"efficiency={guide.get('doench_score', 'N/A')}\"")
                lines.append(f"                     /note=\"offtargets={guide.get('cfd_off_targets', 'N/A')}\"")
                
                pos = end + 1
            
            # Sequence
            lines.append(f"ORIGIN")
            seq_num = 1
            for guide in guides:
                seq = guide.get("seq", "")
                for chunk in wrap(seq, 60):
                    num_str = str(seq_num).rjust(9)
                    spaced = " ".join(wrap(chunk, 10))
                    lines.append(f"{num_str} {spaced}")
                    seq_num += len(chunk)
            
            lines.append(f"//")
            
            with open(output_path, 'w') as f:
                f.write("\n".join(lines))
            
            logger.info(f"Generated simplified GenBank: {output_path}")
            
        except Exception as e:
            logger.error(f"Failed to generate simplified GenBank: {str(e)}")
            raise
    
    def _wrap_field(self, field_name: str, text: str, width: int = 80) -> List[str]:
        """Wrap a text field to specified width."""
        lines = []
        header = f"{field_name:12}"
        
        if len(text) <= width - len(header):
            lines.append(f"{header}{text}")
        else:
            words = text.split()
            current_line = header
            
            for word in words:
                if len(current_line) + 1 + len(word) <= width:
                    if current_line != header:
                        current_line += " "
                    current_line += word
                else:
                    lines.append(current_line)
                    current_line = " " * 12 + word
            
            if current_line.strip():
                lines.append(current_line)
        
        return lines
    
    def _get_taxonomy(self, organism: str) -> str:
        """Get NCBI taxonomy lineage for organism."""
        # Simplified taxonomy mapping
        taxonomy_map = {
            "Homo sapiens": "Eukaryota; Metazoa; Chordata; Craniata; Vertebrata; Euteleostomi; Mammalia; Eutheria; Euarchontoglires; Primates; Haplorrhini; Catarrhini; Hominidae; Homo",
            "Mus musculus": "Eukaryota; Metazoa; Chordata; Craniata; Vertebrata; Euteleostomi; Mammalia; Eutheria; Euarchontoglires; Glires; Rodentia; Myomorpha; Muroidea; Muridae; Murinae; Mus",
            "Rattus norvegicus": "Eukaryota; Metazoa; Chordata; Craniata; Vertebrata; Euteleostomi; Mammalia; Eutheria; Euarchontoglires; Glires; Rodentia; Myomorpha; Muroidea; Muridae; Murinae; Rattus",
            "Drosophila melanogaster": "Eukaryota; Metazoa; Ecdysozoa; Arthropoda; Hexapoda; Insecta; Pterygota; Neoptera; Endopterygota; Diptera; Brachycera; Muscomorpha; Ephydroidea; Drosophilidae; Drosophila; Sophophora",
            "Caenorhabditis elegans": "Eukaryota; Metazoa; Ecdysozoa; Nematoda; Chromadorea; Rhabditida; Rhabditoidea; Rhabditidae; Peloderinae; Caenorhabditis",
            "Saccharomyces cerevisiae": "Eukaryota; Fungi; Dikarya; Ascomycota; Saccharomycotina; Saccharomycetes; Saccharomycetales; Saccharomycetaceae; Saccharomyces",
            "Danio rerio": "Eukaryota; Metazoa; Chordata; Craniata; Vertebrata; Euteleostomi; Actinopterygii; Neopterygii; Teleostei; Neoteleostei; Acanthomorpha; Ovalentaria; Atherinomorphae; Cyprinodontiformes; Cyprinodontoidei; Cyprinidae; Danio"
        }
        
        return taxonomy_map.get(organism, "Eukaryota")


# Convenience functions
def generate_genbank_from_pipeline(pipeline_output: Dict[str, Any], output_path: str) -> None:
    """Generate GenBank file from pipeline output."""
    exporter = GenBankExporter()
    exporter.generate_comprehensive_genbank(pipeline_output, output_path)


def generate_single_gene_genbank(
    gene_symbol: str,
    guides: List[Dict[str, Any]],
    output_path: str,
    gene_metadata: Optional[Dict] = None,
    organism: str = "Homo sapiens"
) -> None:
    """Generate GenBank file for a single gene."""
    exporter = GenBankExporter()
    record = exporter.generate_genbank_record(gene_symbol, guides, gene_metadata, organism)
    
    with open(output_path, 'w') as f:
        f.write(record)
