import os
import pandas as pd
from Bio import SeqIO
import plotly.graph_objects as go
import matplotlib.pyplot as plt
import numpy as np

# --- FASTA Reading Function ---
def read_fasta(fasta_file):
    sequences = []
    headers = []
    for record in SeqIO.parse(fasta_file, "fasta"):
        sequences.append(str(record.seq))
        headers.append(record.id)
    return {"sequences": sequences, "headers": headers}

# --- Sequence Comparison Function ---
def compare_sequences(seqA, seqB):
    # Ensure equal length for comparison (truncate to shorter)
    min_len = min(len(seqA), len(seqB))
    seqA = seqA[:min_len]
    seqB = seqB[:min_len]

    differences = [i for i in range(min_len) if seqA[i] != seqB[i]]
    subsType = [seqB[i] for i in differences]
    return pd.DataFrame({"position": differences, "subsType": subsType})

# --- SNPeek Plotting Function ---
def SNPeek(fastaData, region="Unknown", output_dir=None, showLegend=False, start_pos=None, end_pos=None):
    sequences = fastaData["sequences"]
    seqNames = fastaData["headers"]

    if not sequences:
        print("No sequences to plot.")
        return

    genomeLength = max([len(seq) for seq in sequences])

    # Determine X-axis range (used for initial view in HTML and crop in PNG)
    x_min = start_pos if start_pos is not None else 1
    x_max = end_pos if end_pos is not None else genomeLength

    if output_dir is None:
        output_dir = os.path.join(os.getcwd(), "figures")
    os.makedirs(output_dir, exist_ok=True)

    # --- 1. Generate Interactive HTML (Plotly) ---
    try:
        generate_interactive_plot(sequences, seqNames, region, output_dir, x_min, x_max, genomeLength)
    except Exception as e:
        print(f"Warning: Could not generate interactive plot. {e}")

    # --- 2. Generate Static PNG (Matplotlib) ---
    try:
        generate_static_plot(sequences, seqNames, region, output_dir, showLegend, x_min, x_max)
    except Exception as e:
        print(f"Warning: Could not generate static plot. {e}")


def generate_interactive_plot(sequences, seqNames, region, output_dir, x_min, x_max, genomeLength):
    # Define color map for nucleotide substitutions
    colorMap = {"A": "green", "T": "red", "C": "blue", "G": "yellow", "N": "grey", "-": "black"}
    default_color = "black"

    plot_data = {
        "A": {"x": [], "y": [], "text": []},
        "T": {"x": [], "y": [], "text": []},
        "C": {"x": [], "y": [], "text": []},
        "G": {"x": [], "y": [], "text": []},
        "Other": {"x": [], "y": [], "text": []}
    }

    # Iterate through sequences (skip reference at index 0)
    for i in range(1, len(sequences)):
        diff = compare_sequences(sequences[0], sequences[i])
        
        for _, row in diff.iterrows():
            pos = row["position"] + 1  # 1-based index for plot
            base = row["subsType"]
            key = base if base in plot_data else "Other"
            
            plot_data[key]["x"].append(pos)
            plot_data[key]["y"].append(seqNames[i])
            plot_data[key]["text"].append(f"Seq: {seqNames[i]}<br>Pos: {pos}<br>Mut: {base}")

    fig = go.Figure()

    for base, data in plot_data.items():
        if not data["x"]:
            continue
        color = colorMap.get(base, default_color)
        fig.add_trace(go.Scatter(
            x=data["x"],
            y=data["y"],
            mode='markers',
            name=base,
            marker=dict(symbol='line-ns-open', color=color, size=10, line=dict(width=2)),
            text=data["text"],
            hoverinfo='text'
        ))

    num_sequences = len(sequences)
    fig_height = max(400, num_sequences * 30) 

    fig.update_layout(
        title=f"{region} Nucleotide Differences from Reference ({seqNames[0]})",
        xaxis_title="Genome Position",
        yaxis_title="Sequence",
        template="plotly_white",
        height=fig_height,
        hovermode="closest",
        yaxis=dict(autorange="reversed", type='category'),
        xaxis=dict(range=[x_min, x_max], rangeslider=dict(visible=True))
    )

    save_path = os.path.join(output_dir, "SNPeek.html")
    fig.write_html(save_path)
    print(f"Interactive figure saved at: {save_path}")

def generate_static_plot(sequences, seqNames, region, output_dir, showLegend, x_min, x_max):
    # Define color map for nucleotide substitutions
    colorMap = {"A": "green", "T": "red", "C": "blue", "G": "yellow", "N": "grey", "-": "black"}

    diffList = []
    for i in range(1, len(sequences)):
        diff = compare_sequences(sequences[0], sequences[i])
        diff["color"] = diff["subsType"].map(colorMap).fillna("black")
        diffList.append(diff)

    num_sequences = len(sequences)
    fig_height = max(10, num_sequences * 0.25)

    plt.figure(figsize=(15, fig_height), dpi = 600)

    # Set x-axis limits (Zoom/Crop)
    plt.xlim(x_min, x_max)

    plt.ylim(0.5, num_sequences - 0.5)
    plt.xlabel(f"Genome Position (reference: {seqNames[0]})", fontsize=10)
    plt.yticks(ticks=np.arange(1, num_sequences), labels=seqNames[1:], fontsize=10)
    plt.gca().yaxis.set_tick_params(labelsize=8)
    plt.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.1)

    for i, diff in enumerate(diffList, start=1):
        for _, row in diff.iterrows():
            # Only plot points within the range to save time/resources? 
            # Matplotlib xlim handles the view, but let's be safe.
            if x_min <= row["position"] + 1 <= x_max:
                plt.plot([row["position"] + 1, row["position"] + 1], [i - 0.4, i + 0.4], color=row["color"], marker="|")

    if showLegend:
        plt.legend(list(colorMap.keys()),
                   handlelength=0.8, markerfirst=False,
                   loc="upper left", bbox_to_anchor=(1, 1),
                   facecolor="white", framealpha=0.7)

    plt.title(f"{region} Nucleotide Differences from Reference Sequence")

    save_path = os.path.join(output_dir, "SNPeek.png")
    plt.savefig(save_path)
    plt.close() # Close to free memory
    print(f"Static figure saved at: {save_path}")
