from django.shortcuts import render
from django.db.models import Q, Min, Max
import plotly.graph_objs as go
from lifelines import KaplanMeierFitter
import pandas as pd
from tcgabcd.models import ClinicalSample, Patient, HypoxiaScore

from django.shortcuts import render, redirect
from django.http import HttpResponse
from .forms import CancerTreatmentForm

from django.http import JsonResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
import plotly.graph_objs as go
import numpy as np

# Utility function to dynamically determine Buffa Score range
def get_buffa_score_range():
    min_score = HypoxiaScore.objects.aggregate(min_score=Min('buffa_score'))['min_score']
    max_score = HypoxiaScore.objects.aggregate(max_score=Max('buffa_score'))['max_score'] + 1
    interval = 5
    return int(min_score), int(max_score), interval

def get_winter_score_range():
    min_score = HypoxiaScore.objects.aggregate(min_score=Min('winter_score'))['min_score']
    max_score = HypoxiaScore.objects.aggregate(max_score=Max('winter_score'))['max_score'] + 1
    interval = 5
    return int(min_score), int(max_score), interval

def get_ragnum_score_range():
    min_score = HypoxiaScore.objects.aggregate(min_score=Min('ragnum_score'))['min_score']
    max_score = HypoxiaScore.objects.aggregate(max_score=Max('ragnum_score'))['max_score'] + 1
    interval = 5
    return int(min_score), int(max_score), interval

def get_tmb_nonsynonymous_range():
    """
    Determines the minimum and maximum range for tmb_nonsynonymous, with an interval of 0.25.
    """
    min_score = ClinicalSample.objects.aggregate(min_score=Min('tmb_nonsynonymous'))['min_score']
    max_score = ClinicalSample.objects.aggregate(max_score=Max('tmb_nonsynonymous'))['max_score']
    
    if min_score is None or max_score is None:
        # Default range if no data is available
        min_score, max_score = 0, 1
    
    # Round max_score up to the nearest 0.25 increment
    max_score = round(max_score + 0.25, 2)
    interval = 0.25
    return float(min_score), float(max_score), interval


# Filter configuration
def get_filter():
    return {
        "radiation_therapy": {"value": True, "query": "Radiation Therapy"},
        "chemotherapy": {"value": True, "query": "Chemotherapy"},
        "hormone_therapy": {"value": True, "query": "Hormone Therapy"},
        "sex": {
            "male": {"value": True, "query": "Male"},
            "female": {"value": True, "query": "Female"}
        },
        "os_status": {
            "living": {"value": True, "query": "0:LIVING"},
            "deceased": {"value": True, "query": "1:DECEASED"}
        },
        "race": {
            "white": {"value": True, "query": "White"},
            "black_or_african_american": {"value": True, "query": "Black or African American"},
            "asian": {"value": True, "query": "Asian"},
            "american_indian_or_alaska_native": {"value": True, "query": "American Indian or Alaska Native"},
            "unknown": {"value": True, "query": "Unknown"}
        },
        "subtype": {
            "BRCA_LumA": {"value": True, "query": "BRCA_LumA"},
            "BRCA_Her2": {"value": True, "query": "BRCA_Her2"},
            "BRCA_LumB": {"value": True, "query": "BRCA_LumB"},
            "BRCA_Normal": {"value": True, "query": "BRCA_Normal"},
            "BRCA_Basal": {"value": True, "query": "BRCA_Basal"},
            "NA": {"value": True, "query": "NA"}
        },
        "cancer_type_detailed": {
            "breast_invasive_lobular_carcinoma": {"value": True, "query": "Breast Invasive Lobular Carcinoma"},
            "breast_invasive_ductal_carcinoma": {"value": True, "query": "Breast Invasive Ductal Carcinoma"},
            "breast_invasive_carcinoma_nos": {"value": True, "query": "Breast Invasive Carcinoma (NOS)"},
            "breast_invasive_mixed_mucinous_carcinoma": {"value": True, "query": "Breast Invasive Mixed Mucinous Carcinoma"},
            "metaplastic_breast_cancer": {"value": True, "query": "Metaplastic Breast Cancer"},
            "invasive_breast_carcinoma": {"value": True, "query": "Invasive Breast Carcinoma"},
            "unknown": {"value": True, "query": "Unknown"}
        },
        "ajcc_pathologic_tumor_stage": {
            "stage_x": {"value": True, "query": "STAGE X"},
            "stage_iib": {"value": True, "query": "STAGE IIB"},
            "stage_ia": {"value": True, "query": "STAGE IA"},
            "stage_iiia": {"value": True, "query": "STAGE IIIA"},
            "stage_iia": {"value": True, "query": "STAGE IIA"},
            "stage_i": {"value": True, "query": "STAGE I"},
            "stage_iiic": {"value": True, "query": "STAGE IIIC"},
            "stage_iv": {"value": True, "query": "STAGE IV"},
            "stage_ib": {"value": True, "query": "STAGE IB"},
            "stage_iiib": {"value": True, "query": "STAGE IIIB"},
            "stage_iii": {"value": True, "query": "STAGE III"},
            "stage_ii": {"value": True, "query": "STAGE II"},
            "unknown": {"value": True, "query": "Unknown"}
        },
        "buffa_score_range": {
            "start": None,
            "end": None,
            "interval": 5
        },

        "winter_score_range": {
            "start": None,
            "end": None,
            "interval": 5
        },
        "ragnum_score_range": {
            "start": None,
            "end": None,
            "interval": 5
        },
        "tmb_nonsynonymous_range": {
            "start": None,
            "end": None,
            "interval": 0.25
        }
    }

def bool_filter(data):
    return [vq["query"] for key, vq in data.items() if vq["value"]]

def get_filtered_patients(
    sex_keys, os_status_keys, race_keys, ajcc_pathologic_tumor_stage_keys,
    subtype_keys, cancer_type_detailed_keys, radiation_therapy=None, chemotherapy=None,
    hormone_therapy=None, buffa_start=None, buffa_end=None,
    winter_start=None, winter_end=None, ragnum_start=None, ragnum_end=None,
    tmb_nonsynonymous_start=None, tmb_nonsynonymous_end=None
):
    # Start building the query
    query = Q(clinical_data__sex__in=sex_keys) & Q(clinical_data__os_status__in=os_status_keys)

    # Race filtering
    if "Unknown" in race_keys:
        query &= (Q(clinical_data__race__in=race_keys) | Q(clinical_data__race__isnull=True))
    else:
        query &= Q(clinical_data__race__in=race_keys)

    # Subtype filtering
    if "NA" in subtype_keys:
        query &= (Q(clinical_data__subtype__in=subtype_keys) | Q(clinical_data__subtype__isnull=True))
    else:
        query &= Q(clinical_data__subtype__in=subtype_keys)

    # AJCC Tumor Stage filtering
    if "Unknown" in ajcc_pathologic_tumor_stage_keys:
        query &= (Q(clinical_data__ajcc_pathologic_tumor_stage__in=ajcc_pathologic_tumor_stage_keys) |
                  Q(clinical_data__ajcc_pathologic_tumor_stage__isnull=True))
    else:
        query &= Q(clinical_data__ajcc_pathologic_tumor_stage__in=ajcc_pathologic_tumor_stage_keys)

    # Fetch patients with the necessary joins and filters
    patients = Patient.objects.filter(query).select_related('clinical_data').prefetch_related('treatments', 'clinical_samples').distinct()

    # Apply treatment filtering
    if radiation_therapy or chemotherapy or hormone_therapy:
        treatment_query = Q()
        if radiation_therapy:
            treatment_query |= Q(treatments__treatment_type="Radiation Therapy")
        if chemotherapy:
            treatment_query |= Q(treatments__treatment_type="Chemotherapy")
        if hormone_therapy:
            treatment_query |= Q(treatments__treatment_type="Hormone Therapy")

        # Add condition to include "NA" for unknown treatments
        if all([radiation_therapy, chemotherapy, hormone_therapy]):
            treatment_query |= Q(
                ~Q(treatments__treatment_type__in=["Radiation Therapy", "Chemotherapy", "Hormone Therapy"]) |
                Q(treatments__treatment_type__isnull=True)
            )
        patients = patients.filter(treatment_query).distinct()

    filtered_patients = []
    for patient in patients:
        include_patient = True

        # Check Hypoxia scores
        if hasattr(patient, 'hypoxia_scores') and patient.hypoxia_scores is not None:
            buffa_score = patient.hypoxia_scores.buffa_score
            winter_score = patient.hypoxia_scores.winter_score
            ragnum_score = patient.hypoxia_scores.ragnum_score

            if buffa_score is not None and not (buffa_start <= buffa_score < buffa_end):
                include_patient = False
            if winter_score is not None and not (winter_start <= winter_score < winter_end):
                include_patient = False
            if ragnum_score is not None and not (ragnum_start <= ragnum_score < ragnum_end):
                include_patient = False

        # Check `tmb_nonsynonymous` in ClinicalSample
        if include_patient and tmb_nonsynonymous_start is not None and tmb_nonsynonymous_end is not None:
            tmb_values = [
                sample.tmb_nonsynonymous for sample in patient.clinical_samples.all()
                if sample.tmb_nonsynonymous is not None and tmb_nonsynonymous_start <= sample.tmb_nonsynonymous < tmb_nonsynonymous_end
            ]
            if not tmb_values and any(sample.tmb_nonsynonymous is not None for sample in patient.clinical_samples.all()):
                include_patient = False

        # Cancer Type Detailed filtering
        if include_patient and cancer_type_detailed_keys:
            cancer_type_values = [
                sample.cancer_type_detailed for sample in patient.clinical_samples.all()
                if sample.cancer_type_detailed in cancer_type_detailed_keys or
                   (sample.cancer_type_detailed is None and "Unknown" in cancer_type_detailed_keys)
            ]
            if not cancer_type_values:
                include_patient = False

        if include_patient:
            filtered_patients.append(patient)

    return filtered_patients



def prepare_sex_distribution(patients):
    sex_distribution = {"Male": 0, "Female": 0}
    for patient in patients:
        sex = patient.clinical_data.sex
        sex_distribution[sex] = sex_distribution.get(sex, 0) + 1
    return sex_distribution

def prepare_os_status_distribution(patients):
    os_status_distribution = {"Living": 0, "Deceased": 0}
    for patient in patients:
        os_status_raw = patient.clinical_data.os_status
        os_status = "Living" if os_status_raw == "0:LIVING" else "Deceased"
        os_status_distribution[os_status] = os_status_distribution.get(os_status, 0) + 1
    return os_status_distribution

def prepare_race_distribution(patients):
    race_distribution = {
        "White": 0,
        "Black or African American": 0,
        "Asian": 0,
        "American Indian or Alaska Native": 0,
        "Unknown": 0  # For None or undefined values
    }
    
    for patient in patients:
        race = patient.clinical_data.race or "Unknown"
        if race in race_distribution:
            race_distribution[race] += 1
        else:
            race_distribution["Unknown"] += 1
    
    return race_distribution

def prepare_subtype_distribution(patients):
    subtype_distribution = {
        "BRCA_LumA": 0,
        "BRCA_Her2": 0,
        "BRCA_LumB": 0,
        "BRCA_Normal": 0,
        "BRCA_Basal": 0,
        "NA": 0  # For unknown or undefined values
    }

    for patient in patients:
        subtype = patient.clinical_data.subtype or "NA"
        if subtype in subtype_distribution:
            subtype_distribution[subtype] += 1
        else:
            subtype_distribution["NA"] += 1

    return subtype_distribution

def prepare_cancer_type_detailed_distribution(patients):
    cancer_type_detailed_distribution = {
        "Breast Invasive Lobular Carcinoma": 0,
        "Breast Invasive Ductal Carcinoma": 0,
        "Breast Invasive Carcinoma (NOS)": 0,
        "Breast Invasive Mixed Mucinous Carcinoma": 0,
        "Metaplastic Breast Cancer": 0,
        "Invasive Breast Carcinoma": 0,
        "NA": 0  # For undefined or unknown values
    }

    for patient in patients:
        for sample in patient.clinical_samples.all():
            cancer_type_detailed = sample.cancer_type_detailed or "NA"
            if cancer_type_detailed in cancer_type_detailed_distribution:
                cancer_type_detailed_distribution[cancer_type_detailed] += 1
            else:
                cancer_type_detailed_distribution["NA"] += 1

    return cancer_type_detailed_distribution

def prepare_ajcc_stage_distribution(patients):
    ajcc_stage_distribution = {
        "STAGE X": 0,
        "STAGE IIB": 0,
        "STAGE IA": 0,
        "STAGE IIIA": 0,
        "STAGE IIA": 0,
        "STAGE I": 0,
        "STAGE IIIC": 0,
        "STAGE IV": 0,
        "STAGE IB": 0,
        "STAGE IIIB": 0,
        "STAGE III": 0,
        "STAGE II": 0,
        "Unknown": 0
    }
    
    for patient in patients:
        stage = patient.clinical_data.ajcc_pathologic_tumor_stage or "Unknown"
        if stage in ajcc_stage_distribution:
            ajcc_stage_distribution[stage] += 1
        else:
            ajcc_stage_distribution["Unknown"] += 1

    return ajcc_stage_distribution

def prepare_treatment_distributions(patients,radiation_therapy,chemotherapy,hormone_therapy):
    treatment_distributions = {
        "Radiation Therapy": {"Yes": 0, "No": 0, "NA": 0},
        "Chemotherapy": {"Yes": 0, "No": 0, "NA": 0},
        "Hormone Therapy": {"Yes": 0, "No": 0, "NA": 0},
    }
    for patient in patients:
        treatments = {treatment.treatment_type: treatment for treatment in patient.treatments.all()}
        for treatment_type in treatment_distributions.keys():
            if treatment_type in treatments:
                if treatment_type == "Radiation Therapy" and radiation_therapy['value']:
                    treatment_distributions[treatment_type]["Yes"] += 1
                elif treatment_type == "Chemotherapy" and chemotherapy['value']:
                    treatment_distributions[treatment_type]["Yes"] += 1
                elif treatment_type == "Hormone Therapy" and hormone_therapy['value']:
                    treatment_distributions[treatment_type]["Yes"] += 1
            elif patient.treatments.exists():
                treatment_distributions[treatment_type]["No"] += 1
            else:
                treatment_distributions[treatment_type]["NA"] += 1
    return treatment_distributions

# Refined prepare_buffa_score_distribution function
def prepare_buffa_score_distribution(patients, start=0, end=100, interval=5):
    # Initialize the distribution dictionary for the specified range only
    buffa_score_distribution = {}
    
    # Create bins strictly within the specified start and end range
    current = start
    while current < end:
        bin_key = f"{int(current)} to {int(current + interval)}"
        buffa_score_distribution[bin_key] = 0
        current += interval

    # Counter for patients without buffa_score
    na_count = 0

    # Populate the distribution only for scores within the specified range
    for patient in patients:
        if hasattr(patient, 'hypoxia_scores') and patient.hypoxia_scores and patient.hypoxia_scores.buffa_score is not None:
            hypoxia_score = patient.hypoxia_scores.buffa_score
            
            # Only consider scores within the specified range
            if start <= hypoxia_score < end:
                # Calculate the bin starting point based on the interval
                lower_bound = start + ((hypoxia_score - start) // interval) * interval
                bin_key = f"{int(lower_bound)} to {int(lower_bound + interval)}"
                
                # Ensure the calculated bin_key exists
                if bin_key not in buffa_score_distribution:
                    buffa_score_distribution[bin_key] = 0
                buffa_score_distribution[bin_key] += 1
        else:
            # Increment the "NA" counter for patients without a buffa_score
            na_count += 1

    return buffa_score_distribution, na_count

def prepare_winter_score_distribution(patients, start=0, end=100, interval=5):
    winter_score_distribution = {}
    current = start
    while current < end:
        bin_key = f"{int(current)} to {int(current + interval)}"
        winter_score_distribution[bin_key] = 0
        current += interval

    na_count = 0
    for patient in patients:
        if hasattr(patient, 'hypoxia_scores') and patient.hypoxia_scores and patient.hypoxia_scores.winter_score is not None:
            winter_score = patient.hypoxia_scores.winter_score
            if start <= winter_score < end:
                lower_bound = start + ((winter_score - start) // interval) * interval
                bin_key = f"{int(lower_bound)} to {int(lower_bound + interval)}"
                if bin_key not in winter_score_distribution:
                    winter_score_distribution[bin_key] = 0
                winter_score_distribution[bin_key] += 1
        else:
            na_count += 1

    return winter_score_distribution, na_count

def prepare_ragnum_score_distribution(patients, start=0, end=100, interval=5):
    ragnum_score_distribution = {}
    current = start
    while current < end:
        bin_key = f"{int(current)} to {int(current + interval)}"
        ragnum_score_distribution[bin_key] = 0
        current += interval

    na_count = 0
    for patient in patients:
        if hasattr(patient, 'hypoxia_scores') and patient.hypoxia_scores and patient.hypoxia_scores.ragnum_score is not None:
            ragnum_score = patient.hypoxia_scores.ragnum_score
            if start <= ragnum_score < end:
                lower_bound = start + ((ragnum_score - start) // interval) * interval
                bin_key = f"{int(lower_bound)} to {int(lower_bound + interval)}"
                if bin_key not in ragnum_score_distribution:
                    ragnum_score_distribution[bin_key] = 0
                ragnum_score_distribution[bin_key] += 1
        else:
            na_count += 1

    return ragnum_score_distribution, na_count

def prepare_tmb_nonsynonymous_distribution(patients, start, end, interval=0.25):
    # Initialize the distribution dictionary for the specified range only
    tmb_nonsynonymous_distribution = {}
    current = start
    while current < end and current < 4.5:
        bin_key = f"{round(current, 2)} to {round(current + interval, 2)}"
        tmb_nonsynonymous_distribution[bin_key] = 0
        current += interval
    
    # Add a single bin for all values above `4.5`
    if end > 4.5:
        tmb_nonsynonymous_distribution[">4.5"] = 0
    
    na_count = 0

    # Populate the distribution, grouping values >4.5 into `>4.5`
    for patient in patients:
        for sample in patient.clinical_samples.all():
            tmb_nonsynonymous = sample.tmb_nonsynonymous
            if tmb_nonsynonymous is not None:
                if tmb_nonsynonymous > 4.5:
                    # Group all values above 4.5 in the ">4.5" bin
                    tmb_nonsynonymous_distribution[">4.5"] += 1
                else:
                    # Calculate the bin starting point based on the interval
                    lower_bound = start + ((tmb_nonsynonymous - start) // interval) * interval
                    bin_key = f"{round(lower_bound, 2)} to {round(lower_bound + interval, 2)}"
                    
                    # Ensure the calculated bin_key exists before updating
                    if bin_key in tmb_nonsynonymous_distribution:
                        tmb_nonsynonymous_distribution[bin_key] += 1
                    else:
                        pass
            else:
                # Increment the "NA" counter for samples without a tmb_nonsynonymous score
                na_count += 1

    return tmb_nonsynonymous_distribution, na_count

from tcgabcd.models import Mutation

import pandas as pd

def prepare_mutation_distribution(patients):
    """
    Prepare the mutation count distribution for the filtered patients.

    :param patients: List of patient objects to process.
    :return: A tuple containing the mutation count distribution dictionary and the NA count.
    """
    # Define mutation count bins and their labels (ranges similar to the CSV approach)
    bins = [0, 11, 21, 31, 41, 51, 61, 71, 81, 91, 101, 111, 121, 131, float("inf")]
    labels = ["≤10", "10-20", "20-30", "30-40", "40-50", "50-60", "60-70", "70-80", 
              "80-90", "90-100", "100-110", "110-120", "120-130", ">130"]
    
    # Initialize mutation count distribution
    mutation_count_distribution = {label: 0 for label in labels}
    
    # Initialize NA count
    na_count = 0

    patient_mutation_mapping = {}

    # Define coding mutation types as per your original code
    coding_mutations = [
        'Missense_Mutation', 'Nonsense_Mutation', 'Frame_Shift_Del', 
        'Frame_Shift_Ins', 'In_Frame_Del', 'In_Frame_Ins', 'Splice_Site', 
        'Translation_Start_Site', 'Nonstop_Mutation', 'Splice_Region'
    ]
    
    # Iterate through each patient to count mutations
    for patient in patients:
        # Query mutations for the current patient, filtering for coding mutations
        mutations = Mutation.objects.filter(
            tumor_sample_barcode=patient,
            variant_classification__in=coding_mutations
        ).exclude(
            hugo_symbol__isnull=True,
            chromosome__isnull=True,
            start_position__isnull=True,
            end_position__isnull=True,
            variant_classification__isnull=True,
            variant_type__isnull=True,
            tumor_sample_barcode__isnull=True
        )

        # Manually remove duplicates in Python based on mutation fields
        unique_mutations = set()
        for mutation in mutations:
            mutation_key = (mutation.hugo_symbol, mutation.chromosome, mutation.start_position, 
                            mutation.end_position, mutation.variant_classification, mutation.variant_type)
            unique_mutations.add(mutation_key)

        # Count mutations for this patient
        mutation_count = len(unique_mutations)
        patient_mutation_mapping[patient] = mutation_count
        
        if mutation_count > 0:
            # Determine the appropriate bin for the mutation count
            for i in range(len(bins) - 1):
                if bins[i] <= mutation_count < bins[i + 1]:
                    mutation_count_distribution[labels[i]] += 1
                    break
        else:
            # Increment the NA count for patients with no mutations
            na_count += 1

    return mutation_count_distribution, na_count, patient_mutation_mapping

from django.db.models import F, Sum, FloatField
from tcgabcd.models import Cna_hg19

def prepare_fga_distribution(patients):
    """
    Prepare the Fraction Genome Altered (FGA) distribution for the filtered patients.

    :param patients: List of patient objects to process.
    :return: A tuple containing the FGA distribution dictionary and the NA count.
    """
    # Define FGA bins and labels
    bins = [0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 
            0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 1, float("inf")]
    labels = ['≤0.05'] + [f'{bins[i]:.2f}-{bins[i + 1]:.2f}' for i in range(1, len(bins) - 2)] + ['>0.9']

    # Initialize FGA distribution
    fga_distribution = {label: 0 for label in labels}
    
    # Initialize NA count
    na_count = 0
    patient_fga_mapping = {}

    # Iterate through each patient
    for patient in patients:
        # Query CNA data for the current patient
        cna_data = Cna_hg19.objects.filter(patient=patient).annotate(
            segment_length=F('loc_end') - F('loc_start')
        )

        # Calculate total genome length and altered segment length
        total_length = cna_data.aggregate(total=Sum('segment_length', output_field=FloatField()))['total'] or 0
        altered_length = cna_data.filter(
            seg_mean__gte=0.2
        ).aggregate(total=Sum('segment_length', output_field=FloatField()))['total'] or 0
        altered_length += cna_data.filter(
            seg_mean__lte=-0.2
        ).aggregate(total=Sum('segment_length', output_field=FloatField()))['total'] or 0

        # Calculate FGA for the patient
        if total_length > 0:
            fga = round(altered_length / total_length, 4)
            patient_fga_mapping[patient] = fga
            # Assign FGA to the appropriate bin
            for i in range(len(bins) - 1):
                if bins[i] <= fga < bins[i + 1]:
                    fga_distribution[labels[i]] += 1
                    break
        else:
            # Increment NA count if total genome length is 0
            na_count += 1
            patient_fga_mapping[patient] = None
    

    return fga_distribution, na_count, patient_fga_mapping

from scipy.stats import pearsonr, spearmanr

def prepare_mutation_vs_fga_distribution(patient_mutation_mapping, patient_fga_mapping):
    """
    Generate a Plotly scatter plot of mutation count vs. fraction genome altered (FGA).
    :param patient_mutation_mapping: Dictionary with patient objects as keys and mutation counts as values.
    :param patient_fga_mapping: Dictionary with patient objects as keys and FGA values as values.
    :return: HTML representation of the Plotly figure.
    """
    # Extract mutation counts and FGA values
    mutation_counts = []
    fga_values = []
    
    # Create a dictionary to track sample counts for each unique FGA value
    fga_sample_count = {}

    for patient, mutation_count in patient_mutation_mapping.items():
        fga = patient_fga_mapping.get(patient)
        if fga is not None and mutation_count is not None and mutation_count > 0:  # Exclude mutation count 0
            mutation_counts.append(mutation_count)
            fga_values.append(fga)

            # Count samples for each FGA value
            if fga in fga_sample_count:
                fga_sample_count[fga] += 1
            else:
                fga_sample_count[fga] = 1

    # Convert to numpy arrays for easier calculations
    fga_values = np.array(fga_values)
    mutation_counts = np.array(mutation_counts)

    # Map the number of samples (fga_sample_count) to each data point
    sample_counts = [fga_sample_count[fga] for fga in fga_values]

    # Create hover text for each point based on the individual data
    text = []
    for mutation_count, fga_value, sample_count in zip(mutation_counts, fga_values, sample_counts):
        # Display number of samples, FGA, and mutation count for each data point
        text.append(f'Number of Samples: {sample_count}<br>'  # Display the sample count
                    f'Fraction Genome Altered: {fga_value:.2f}<br>'
                    f'Mutation Count: {mutation_count}')

    fig = go.Figure(
    data=go.Scatter(
        x=fga_values,
        y=mutation_counts,
        mode='markers',
        marker=dict(
            color=sample_counts,  # Use sample counts for color
            colorscale='Tealgrn',
            colorbar=dict(
                thickness=15,
                len=0.8,
                title=dict(
                    text='Sample Count',  # Label for the colorbar
                    side='right',  # Position title on the right
                    font=dict(size=12)  # Adjust title font size
                ),
                tickvals=np.linspace(max(1, min(sample_counts)), max(sample_counts), num=5),  # Colorbar ticks start from 1
                ticktext=[str(int(val)) for val in np.linspace(max(1, min(sample_counts)), max(sample_counts), num=5)],  # Add text to each tick
                tickmode='array',
            ),
            opacity=0.7,
            line=dict(width=1, color='black')
        ),
        text=text,
        hoverinfo='text'
    )
)

    # Add layout configurations
    fig.update_layout(
        xaxis=dict(
            title='Fraction Genome Altered',
            tickmode='linear',
            tick0=0,
            dtick=0.1,
            tickangle=-45,
            tickfont=dict(size=10),
            showline=True,
            linecolor='black',
            linewidth=1.5
        ),
        yaxis=dict(
            title='Mutation Count',
            tickfont=dict(size=10),
            showline=True,
            linecolor='black',
            linewidth=1.5
        ),
        template="plotly_white",
        autosize=True,
        margin=dict(l=20, r=40, t=20, b=20),
        showlegend=False,
        modebar=dict(orientation='v')
    )

    # Configure the plotly figure settings
    config = {
        'responsive': True,
        'displaylogo': False,
        'displayModeBar': True,
        'modeBarButtonsToAdd': [],
        'modeBarButtonsToRemove': ['lasso2d', 'select2d'],
        'toImageButtonOptions': {
            'filename': 'Mutation_Count_vs_Fraction_Genome_Altered',
            'width': 800,
            'scale': 2
        },
    }

    # Return the HTML representation of the Plotly figure
    return fig.to_html(full_html=False, config=config, include_plotlyjs=False)



from tcgabcd.models import SvData
from collections import defaultdict

def prepare_sv_vs_sample_count(patients):
    """
    Generate a Plotly scatter plot of Total Structural Variants vs. Sample Count for all Hugo symbols 
    associated with the filtered patients. If multiple Hugo symbols share the same data point, 
    the hover text will display all symbols in that point, formatted with an ellipsis for long lists.
    
    :param patients: List of patient objects to process.
    :return: HTML representation of the Plotly figure.
    """
    # Filter the SvData records for the provided patients
    sv_data = SvData.objects.filter(patient__in=patients).select_related('site1_gene', 'site2_gene')

    # Collect all Hugo symbols for the filtered patients
    site1_symbols = sv_data.values_list('site1_gene__hugo_symbol', flat=True)
    site2_symbols = sv_data.values_list('site2_gene__hugo_symbol', flat=True)
    
    # Combine the Hugo symbols from both site1 and site2
    hugo_symbols = set(site1_symbols).union(site2_symbols)

    # Count total structural variants for each Hugo symbol
    symbol_counts = defaultdict(int)
    patient_counts = defaultdict(set)  # To track unique patients for each symbol

    for entry in sv_data:
        site1_symbol = entry.site1_gene.hugo_symbol
        site2_symbol = entry.site2_gene.hugo_symbol
        patient_id = entry.patient.patient_id
        
        # Increment structural variant count for both site1 and site2 symbols
        symbol_counts[site1_symbol] += 1
        symbol_counts[site2_symbol] += 1
        
        # Add unique patient to the corresponding symbol's set
        patient_counts[site1_symbol].add(patient_id)
        patient_counts[site2_symbol].add(patient_id)
    
    # Prepare data for plotting
    sample_counts = [len(patient_counts[symbol]) for symbol in hugo_symbols]
    structural_variants = [symbol_counts[symbol] for symbol in hugo_symbols]

    # Create a mapping of (Sample Count, Structural Variants) -> List[Hugo Symbols]
    point_to_symbols = defaultdict(list)
    for symbol, samples, variants in zip(hugo_symbols, sample_counts, structural_variants):
        point_to_symbols[(samples, variants)].append(symbol)

    # Prepare the scatter plot data
    unique_points = list(point_to_symbols.keys())
    unique_sample_counts = [point[0] for point in unique_points]
    unique_variants = [point[1] for point in unique_points]

    # Create hover text for each unique point, showing only a preview of Hugo symbols if too many
    hover_texts = []
    for samples, variants in unique_points:
        symbols = point_to_symbols[(samples, variants)]
        # Truncate long lists and show the first few with an ellipsis
        if len(symbols) > 5:
            display_symbols = ", ".join(symbols[:5]) + "..."
        else:
            display_symbols = ", ".join(symbols)
        
        hover_texts.append(f"Sample Count: {samples}<br>"
                           f"Total Structural Variants: {variants}<br>"
                           f"Hugo Symbols: {display_symbols}")
        
    # Create the scatter plot
    fig = go.Figure(
        data=go.Scatter(
            x=unique_sample_counts,
            y=unique_variants,
            mode='markers',
            marker=dict(
                color=unique_variants,
                colorscale='Viridis',
                size=6,
                colorbar=dict(
                    title=dict(
                        text='Structural Variants',  # Label for the colorbar
                        side='right',  # Position title on the right
                        font=dict(size=12)  # Adjust title font size
                    ),
                    thickness=15
                ),
                opacity=0.8
            ),
            text=hover_texts,
            hoverinfo='text'
        )
    )

    # Update layout for better appearance
    fig.update_layout(
        xaxis=dict(
            title="Sample Count",
            showline=True,
            linecolor='black',
            linewidth=1.5
        ),
        yaxis=dict(
            title="Total Structural Variants",
            showline=True,
            linecolor='black',
            linewidth=1.5
        ),
        template="plotly_white",
        autosize=True,
        margin=dict(l=50, r=50, t=50, b=50),
        modebar=dict(orientation='v')
    )

    # Configure Plotly settings
    config = {
        'responsive': True,
        'displaylogo': False,
        'displayModeBar': True,
        'modeBarButtonsToAdd': [],
        'modeBarButtonsToRemove': ['lasso2d', 'select2d'],
        'toImageButtonOptions': {
            'filename': 'SV_vs_Sample_Count',
            'width': 800,
            'scale': 2
        },
    }

    # Return the HTML representation of the Plotly figure
    return fig.to_html(full_html=False, config=config, include_plotlyjs=False)


import plotly.graph_objs as go

import plotly.graph_objects as go

def generate_pie_chart(data, title):
    # Define specific colors for key labels, ensuring they are distinct
    color_map = {
        'Yes': '#2E7D32',
        'No': '#B71C1C',
        'NA': '#E65100',
        'Living': '#2E7D32',
        'Deceased': '#B71C1C',
        'Male': '#1565C0',
        'Female': '#880E4F',
    }
    
    fallback_colors = [
        '#D84315', '#558B2F', '#AD1457', '#00838F', '#6A1B9A', '#EF6C00', '#283593', '#C2185B',
        '#2E7D32', '#8E24AA', '#C62828', '#0277BD', '#5D4037', '#F9A825', '#4A148C', '#9E9D24',
        '#795548', '#0097A7', '#F57F17', '#37474F'
    ]
    
    # Assign colors to labels
    colors = [
        color_map.get(label, fallback_colors[i % len(fallback_colors)]) 
        for i, label in enumerate(data.keys())
    ]

    # Create the pie chart
    fig = go.Figure(
    data=[
        go.Pie(
            labels=list(data.keys()),
            values=list(data.values()),
            hovertext=list(data.keys()),
            hoverinfo='label+percent+value',  # Show label, percent, and value on hover
            textinfo='none',  # Hide text on slices
            marker=dict(
                colors=colors,  # Use predefined colors
                line=dict(color='#FFFFFF', width=1)  # White border for clean separation
            ),
            pull=[0.01 if i % 2 == 0 else 0 for i in range(len(data))],  # Slight pull for alternate slices
        )
    ]
)

    # Layout improvements for fixed size and clean design
    fig.update_layout(
        autosize=True,  # Enable automatic resizing
        margin=dict(l=0, r=0, t=0, b=0),  # Minimize margins
        height=None,  # Allow height to adjust dynamically
        width=None, 
        legend=dict(
            font=dict(size=12, color='#4A4A4A'),
            orientation="h",  # Horizontal legend
            yanchor="top",
            xanchor="center",
            x=0.3,  # Center the legend below the chart
            y=-0.2,
            traceorder="normal"
        ),
        paper_bgcolor="#F9F9F9",  # Light gray background for a professional look
        plot_bgcolor="#FFFFFF"  # White background for the chart area
    )

    # Responsive configuration for interactivity
    config = {
        'responsive': True,
        'displaylogo': False,
        'displayModeBar': True,
        'modeBarButtonsToRemove': ['lasso2d', 'select2d'],
        'toImageButtonOptions': {
            'filename': title,
            'width': 800,
            'scale': 2
        }
    }

    return fig.to_html(full_html=False, config=config, include_plotlyjs=False)



def generate_bar_chart(data, title, x_title, y_title, na_count):
    # Sort keys with "<start" first, followed by numeric ranges, and ">end" last
    ordered_keys = sorted(data.keys(), key=lambda x: (float(x.split(" ")[0]) if "to" in x else -float("inf") if "<" in x else float("inf")))
    ordered_values = [data[key] for key in ordered_keys]

    # Use a color scale to generate dark gradient colors for bars
    colorscale = "Viridis"
    # color_values = np.linspace(0, 1, len(ordered_keys))  # Spread the gradient across the bars

    # Create the bar chart
    fig = go.Figure(
        data=[
            go.Bar(
                x=ordered_keys,
                y=ordered_values,
                width=.8, 
                marker=dict(
                    color=ordered_values,  # Use gradient for bar colors
                    colorscale=colorscale,
                    line=dict(color='rgba(0, 0, 0, 1)', width=1.5)  # Black border for contrast
                ),
                text=ordered_values,  # Display values on top of bars
                textposition='outside',  # Position text outside bars
                textfont=dict(size=12, color='#333333')  # Dark text for readability on light background
            )
        ]
    )


    # Update layout for titles, labels, and annotations with axis borders
    fig.update_layout(
        
        xaxis=dict(
            title=x_title,
            tickangle=-45,  # Tilt x-axis labels for better fit
            tickfont=dict(size=10),
            showline=True,   # Enable x-axis line
            linecolor='black',  # Set x-axis line color
            linewidth=1.5     # Set x-axis line width
        ),
        yaxis=dict(
            title=y_title,
            tickfont=dict(size=10),
            showline=True,   # Enable y-axis line
            linecolor='black',  # Set y-axis line color
            linewidth=1.5     # Set y-axis line width
        ),
        template="plotly_white",  # Light background theme
        autosize=True,
        margin=dict(l=20, r=40, t=20, b=20),
        showlegend=False,
        modebar=dict(orientation='v')
    )

    # Add annotation for NA count if applicable
    if na_count > 0:
        fig.add_annotation(
            x=1, y=1.05,  # Position in top-right corner above chart
            xref="paper", yref="paper",
            text=f"NA: {na_count}",
            showarrow=False,
            font=dict(size=14, color="black"),
            bgcolor="rgba(255, 255, 255, 0.8)",  # Light background for readability
            bordercolor="black",
            borderwidth=1,
            borderpad=4
        )
    
    config = {
        'responsive': True,
        'displaylogo': False,
        'displayModeBar': True,  
        'modeBarButtonsToAdd': [],  
        'modeBarButtonsToRemove': ['lasso2d', 'select2d'],
        'toImageButtonOptions': {
            'filename': title, 
            'width': 800,
            'scale': 2
        },
    }

    return fig.to_html(full_html=False, config=config,include_plotlyjs=False)

def prepare_km_data(patients, status_field, months_field, title):
    # Filter out patients with None values in either status or months fields
    survival_data = [
        (getattr(p.clinical_data, months_field), getattr(p.clinical_data, status_field) in ['1:DECEASED', '1:Recurred/Progressed', '1:PROGRESSION', '1:DEAD WITH TUMOR'])
        for p in patients
        if getattr(p.clinical_data, months_field) is not None and getattr(p.clinical_data, status_field) is not None
    ]

    if not survival_data:
        return None  # No valid data available for KM plot

    # Convert to DataFrame for lifelines KM analysis
    df = pd.DataFrame(survival_data, columns=['months', 'event_observed'])
    kmf = KaplanMeierFitter()
    kmf.fit(durations=df['months'], event_observed=df['event_observed'])

    # Calculate patients at risk for each time point
    patients_at_risk = []
    for time in kmf.survival_function_.index:
        patients_at_risk.append(df[df['months'] >= time].shape[0])

    # Convert survival probabilities to percentages
    survival_percentages = kmf.survival_function_['KM_estimate'] * 100

    # Create custom hover text with months, survival probability in percentage, and patients at risk
    hover_text = [
        f"<b>Months:</b> {time}<br><b>Survival Probability:</b> {surv_prob:.2f}%<br><b>Patients at Risk:</b> {risk}"
        for time, surv_prob, risk in zip(kmf.survival_function_.index, survival_percentages, patients_at_risk)
    ]

    # Create Plotly KM plot with enhanced design and visibility
    fig = go.Figure()

    # Add a visible and bold survival probability line with gradient effect
    fig.add_trace(go.Scatter(
        x=kmf.survival_function_.index,
        y=survival_percentages,
        mode='lines+markers',
        name='Survival Probability',
        line=dict(color='rgb(58, 79, 114)', width=4, dash='solid'),  # Thick, dark blue line
        marker=dict(
            size=6,
            color=survival_percentages,  # Gradient marker color based on survival probability
            colorscale="Hot",
            showscale=True,
            cmin=0,
            cmax=100,
            colorbar=dict(title="Survival %"),
            line=dict(width=2, color='black')
        ),
        text=hover_text,
        hoverinfo="text"
    ))

    # Add a shaded area for confidence interval to increase visibility
    if kmf.confidence_interval_ is not None:
        ci_upper = kmf.confidence_interval_['KM_estimate_upper_0.95'] * 100
        ci_lower = kmf.confidence_interval_['KM_estimate_lower_0.95'] * 100

        fig.add_trace(go.Scatter(
            x=kmf.survival_function_.index.tolist() + kmf.survival_function_.index[::-1].tolist(),
            y=ci_upper.tolist() + ci_lower[::-1].tolist(),
            fill='toself',
            fillcolor='rgba(173, 216, 230, 0.3)',  # Light blue shade for confidence interval
            line=dict(color='rgba(255,255,255,0)'),
            showlegend=False,
            hoverinfo="skip"
        ))

    # Update layout with legend completely outside the plot
    fig.update_layout(
        
        xaxis=dict(
            title="Time (Months)",
            showgrid=True,
            gridcolor="lightgrey",
            tickmode="linear",
            tick0=0,
            dtick=24,  # Tick every 12 months (1 year)
            zeroline=False,
            linecolor="grey",
            rangemode='tozero'
        ),
        yaxis=dict(
            title="Survival Probability (%)",
            showgrid=True,
            gridcolor="lightgrey",
            zeroline=False,
            range=[0, 100],  # Set y-axis range to 0-100%
            linecolor="grey"
        ),
        legend=dict(
            yanchor="top",
            y=1.12,
            xanchor="left",
            x=0.8,  # Position legend completely outside the plot area to the right
            font=dict(size=12),
            bgcolor="rgba(255,255,255,0.8)"  # Slightly transparent background for visibility
        ),
        plot_bgcolor='white',
        hoverlabel=dict(
            bgcolor="white",
            font_size=14,
            font_family="Arial"
        ),
        autosize=True,
        margin=dict(l=20, r=140, t=20, b=20),
        modebar=dict(orientation='v')
    )

    config = {
        'responsive': True,
        'displaylogo': False,
        'displayModeBar': True,  
        'modeBarButtonsToAdd': [],  
        'modeBarButtonsToRemove': ['lasso2d', 'select2d'],
        'toImageButtonOptions': {
            'filename': title, 
            'width': 800,
            'scale': 2
        },
    }



    return fig.to_html(full_html=False, config=config,include_plotlyjs=False)

@csrf_exempt
def tcgaQuery(request):
    filter = get_filter()
    
    # Initialize the form and update filter values based on POST data
    form = CancerTreatmentForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        # Update boolean fields for treatment options
        filter["radiation_therapy"]["value"] = form.cleaned_data["radiation_therapy"]
        filter["chemotherapy"]["value"] = form.cleaned_data["chemotherapy"]
        filter["hormone_therapy"]["value"] = form.cleaned_data["hormone_therapy"]

        # Update multi-choice fields
        selected_sex = form.cleaned_data["sex"]

        if selected_sex:
            filter["sex"]["male"]["value"] = "male" in selected_sex
            filter["sex"]["female"]["value"] = "female" in selected_sex
        else:
            filter["sex"]["male"]["value"] = True
            filter["sex"]["female"]["value"] = True

        selected_os_status = form.cleaned_data["os_status"]
        

        if selected_os_status:
            filter["os_status"]["living"]["value"] = "living" in selected_os_status
            filter["os_status"]["deceased"]["value"] = "deceased" in selected_os_status
        else:
            filter["os_status"]["living"]["value"] = True
            filter["os_status"]["deceased"]["value"] = True

        selected_race = form.cleaned_data["race"]
        if selected_race:
            for race_key in filter["race"]:
                filter["race"][race_key]["value"] = race_key in selected_race
        else:
            for race_key in filter["race"]:
                filter["race"][race_key]["value"] = True

        selected_subtype = form.cleaned_data["subtype"]
        if selected_subtype:
            for subtype_key in filter["subtype"]:
                filter["subtype"][subtype_key]["value"] = subtype_key in selected_subtype
        else:
            for subtype_key in filter["subtype"]:
                filter["subtype"][subtype_key]["value"] = True

        selected_cancer_type = form.cleaned_data["cancer_type_detailed"]
        if selected_cancer_type:
            for cancer_type_key in filter["cancer_type_detailed"]:
                filter["cancer_type_detailed"][cancer_type_key]["value"] = cancer_type_key in selected_cancer_type
        else:
            for cancer_type_key in filter["cancer_type_detailed"]:
                filter["cancer_type_detailed"][cancer_type_key]["value"] = True

        selected_stage = form.cleaned_data["ajcc_pathologic_tumor_stage"]
        if selected_stage:
            for stage_key in filter["ajcc_pathologic_tumor_stage"]:
                filter["ajcc_pathologic_tumor_stage"][stage_key]["value"] = stage_key in selected_stage
        else:
            for stage_key in filter["ajcc_pathologic_tumor_stage"]:
                filter["ajcc_pathologic_tumor_stage"][stage_key]["value"] = True

        # Update score range fields
        filter["buffa_score_range"]["start"] = form.cleaned_data["buffa_score_start"]
        filter["buffa_score_range"]["end"] = form.cleaned_data["buffa_score_end"]
        filter["buffa_score_range"]["interval"] = form.cleaned_data["buffa_score_interval"]

        filter["winter_score_range"]["start"] = form.cleaned_data["winter_score_start"]
        filter["winter_score_range"]["end"] = form.cleaned_data["winter_score_end"]
        filter["winter_score_range"]["interval"] = form.cleaned_data["winter_score_interval"]

        filter["ragnum_score_range"]["start"] = form.cleaned_data["ragnum_score_start"]
        filter["ragnum_score_range"]["end"] = form.cleaned_data["ragnum_score_end"]
        filter["ragnum_score_range"]["interval"] = form.cleaned_data["ragnum_score_interval"]

        filter["tmb_nonsynonymous_range"]["start"] = form.cleaned_data["tmb_nonsynonymous_start"]
        filter["tmb_nonsynonymous_range"]["end"] = form.cleaned_data["tmb_nonsynonymous_end"]
        filter["tmb_nonsynonymous_range"]["interval"] = form.cleaned_data["tmb_nonsynonymous_interval"]

    # Extract keys for filters
    sex_keys = bool_filter(filter["sex"])
    os_status_keys = bool_filter(filter["os_status"])
    race_keys = bool_filter(filter["race"])
    subtype_keys = bool_filter(filter["subtype"])
    ajcc_pathologic_tumor_stage_keys = bool_filter(filter["ajcc_pathologic_tumor_stage"])
    cancer_type_detailed_keys = bool_filter(filter["cancer_type_detailed"])

    # Retrieve or set default score range values
    if filter["buffa_score_range"]["start"] is None or filter["buffa_score_range"]["end"] is None:
        min_score, max_score, interval = get_buffa_score_range()
        filter["buffa_score_range"]["start"] = min_score
        filter["buffa_score_range"]["end"] = max_score
        filter["buffa_score_range"]["interval"] = interval

    if filter["winter_score_range"]["start"] is None or filter["winter_score_range"]["end"] is None:
        winter_start, winter_end, winter_interval = get_winter_score_range()
        filter["winter_score_range"]["start"] = winter_start
        filter["winter_score_range"]["end"] = winter_end
        filter["winter_score_range"]["interval"] = winter_interval

    if filter["ragnum_score_range"]["start"] is None or filter["ragnum_score_range"]["end"] is None:
        ragnum_start, ragnum_end, ragnum_interval = get_ragnum_score_range()
        filter["ragnum_score_range"]["start"] = ragnum_start
        filter["ragnum_score_range"]["end"] = ragnum_end
        filter["ragnum_score_range"]["interval"] = ragnum_interval

    if filter["tmb_nonsynonymous_range"]["start"] is None or filter["tmb_nonsynonymous_range"]["end"] is None:
        min_score, max_score, interval = get_tmb_nonsynonymous_range()
        filter["tmb_nonsynonymous_range"]["start"] = min_score
        filter["tmb_nonsynonymous_range"]["end"] = max_score
        filter["tmb_nonsynonymous_range"]["interval"] = interval

    # Get the score ranges from the filter
    buffa_start = filter["buffa_score_range"]["start"]
    buffa_end = filter["buffa_score_range"]["end"]
    buffa_interval = filter["buffa_score_range"]["interval"]

    winter_start = filter["winter_score_range"]["start"]
    winter_end = filter["winter_score_range"]["end"]
    winter_interval = filter["winter_score_range"]["interval"]

    ragnum_start = filter["ragnum_score_range"]["start"]
    ragnum_end = filter["ragnum_score_range"]["end"]
    ragnum_interval = filter["ragnum_score_range"]["interval"]

    tmb_nonsynonymous_start = filter["tmb_nonsynonymous_range"]["start"]
    tmb_nonsynonymous_end = filter["tmb_nonsynonymous_range"]["end"]
    tmb_nonsynonymous_interval = filter["tmb_nonsynonymous_range"]["interval"]

    radiation_therapy = filter["radiation_therapy"]["value"]
    chemotherapy = filter["chemotherapy"]["value"]
    hormone_therapy = filter["hormone_therapy"]["value"]

    # Get filtered patients based on all updated filter values
    patients = get_filtered_patients(
        sex_keys=sex_keys,
        os_status_keys=os_status_keys,
        race_keys=race_keys,
        ajcc_pathologic_tumor_stage_keys=ajcc_pathologic_tumor_stage_keys,
        subtype_keys=subtype_keys,
        cancer_type_detailed_keys=cancer_type_detailed_keys,
        radiation_therapy=radiation_therapy,
        chemotherapy=chemotherapy,
        hormone_therapy=hormone_therapy,
        buffa_start=buffa_start, buffa_end=buffa_end,
        winter_start=winter_start, winter_end=winter_end,
        ragnum_start=ragnum_start, ragnum_end=ragnum_end,
        tmb_nonsynonymous_start=tmb_nonsynonymous_start,
        tmb_nonsynonymous_end=tmb_nonsynonymous_end
    )

    # Prepare and render data for each chart
    sex_distribution = prepare_sex_distribution(patients)
    os_status_distribution = prepare_os_status_distribution(patients)
    race_distribution = prepare_race_distribution(patients)
    ajcc_stage_distribution = prepare_ajcc_stage_distribution(patients)
    subtype_distribution = prepare_subtype_distribution(patients)
    cancer_type_detailed_distribution = prepare_cancer_type_detailed_distribution(patients)
    treatment_distributions = prepare_treatment_distributions(patients, filter["radiation_therapy"], filter["chemotherapy"], filter["hormone_therapy"])

    buffa_score_distribution, buffa_na_count = prepare_buffa_score_distribution(patients, start=buffa_start, end=buffa_end, interval=buffa_interval)
    winter_score_distribution, winter_na_count = prepare_winter_score_distribution(patients, start=winter_start, end=winter_end, interval=winter_interval)
    ragnum_score_distribution, ragnum_na_count = prepare_ragnum_score_distribution(patients, start=ragnum_start, end=ragnum_end, interval=ragnum_interval)
    mutation_count_distribution, mutation_na_count, patient_mutation_mapping = prepare_mutation_distribution(patients)
    fraction_count_distribution, fraction_na_count, patient_fga_mapping = prepare_fga_distribution(patients)

    # Generate distribution charts
    sex_chart = generate_pie_chart(sex_distribution, 'Sex Distribution')
    os_status_chart = generate_pie_chart(os_status_distribution, 'OS Status Distribution')
    race_chart = generate_pie_chart(race_distribution, 'Race Distribution')
    ajcc_stage_chart = generate_pie_chart(ajcc_stage_distribution, 'AJCC Tumor Stage Distribution')
    subtype_chart = generate_pie_chart(subtype_distribution, 'Subtype Distribution')
    cancer_type_detailed_chart = generate_pie_chart(cancer_type_detailed_distribution, 'Cancer Type Detailed Distribution')
    buffa_score_chart = generate_bar_chart(buffa_score_distribution, 'Buffa Hypoxia Score', 'Buffa Score Range', 'Number of Patients', buffa_na_count)
    winter_score_chart = generate_bar_chart(winter_score_distribution, 'Winter Hypoxia Score', 'Winter Score Range', 'Number of Patients', winter_na_count)
    ragnum_score_chart = generate_bar_chart(ragnum_score_distribution, 'Ragnum Hypoxia Score', 'Ragnum Score Range', 'Number of Patients', ragnum_na_count)
    mutation_count_chart = generate_bar_chart(mutation_count_distribution, 'Mutation Count', 'Mutation Count Range', 'Number of Patients', mutation_na_count)
    fraction_count_chart = generate_bar_chart(fraction_count_distribution, 'Fraction Genome Altered', 'Fraction Genome Altered Range', 'Number of Patients', fraction_na_count)
    mva_chart = prepare_mutation_vs_fga_distribution(patient_mutation_mapping, patient_fga_mapping)
    radiation_chart = generate_pie_chart(treatment_distributions["Radiation Therapy"], 'Radiation Therapy Distribution')
    chemo_chart = generate_pie_chart(treatment_distributions["Chemotherapy"], 'Chemotherapy Distribution')
    hormone_chart = generate_pie_chart(treatment_distributions["Hormone Therapy"], 'Hormone Therapy Distribution')
    sv_vs_sample = prepare_sv_vs_sample_count(patients)

    # Generate Kaplan-Meier survival curves
    km_plot_os = prepare_km_data(patients, 'os_status', 'os_months', 'Kaplan-Meier Survival Curve - OS')
    km_plot_dss = prepare_km_data(patients, 'dss_status', 'dss_months', 'Kaplan-Meier Survival Curve - DSS')
    km_plot_dfs = prepare_km_data(patients, 'dfs_status', 'dfs_months', 'Kaplan-Meier Survival Curve - DFS')
    km_plot_pfs = prepare_km_data(patients, 'pfs_status', 'pfs_months', 'Kaplan-Meier Survival Curve - PFS')

    # Prepare TMB nonsynonymous score distribution and chart
    tmb_nonsynonymous_distribution, tmb_na_count = prepare_tmb_nonsynonymous_distribution(
        patients, start=tmb_nonsynonymous_start, end=tmb_nonsynonymous_end, interval=tmb_nonsynonymous_interval
    )
    tmb_nonsynonymous_chart = generate_bar_chart(
        tmb_nonsynonymous_distribution, 'TMB Nonsynonymous', 'TMB Score Range', 'Number of Patients', tmb_na_count
    )

    # Render the results in the HTML template
    return render(request, 'tcga_query_results.html', {
        'patients_count': len(patients),
        'sex_chart': sex_chart,
        'os_status_chart': os_status_chart,
        'race_chart': race_chart,
        'ajcc_stage_chart': ajcc_stage_chart,
        'subtype_chart': subtype_chart,
        'cancer_type_detailed_chart': cancer_type_detailed_chart,
        'buffa_score_chart': buffa_score_chart,
        'winter_score_chart': winter_score_chart,
        'ragnum_score_chart': ragnum_score_chart,
        'radiation_chart': radiation_chart,
        'chemo_chart': chemo_chart,
        'hormone_chart': hormone_chart,
        'km_plot_os': km_plot_os,
        'km_plot_dss': km_plot_dss,
        'km_plot_dfs': km_plot_dfs,
        'km_plot_pfs': km_plot_pfs,
        'mutation_count_chart': mutation_count_chart,
        'fga_count_chart': fraction_count_chart,
        'mva_chart': mva_chart,
        'svsample': sv_vs_sample,
        'tmb_nonsynonymous_chart': tmb_nonsynonymous_chart,
        'form': form  # Pass the form to the template
    })

import plotly.graph_objects as go
from plotly.io import to_html
from scipy.stats import spearmanr, pearsonr, linregress
from django.shortcuts import render
from tcgabcd.models import GeneExpression, Gene
def gene_expression_plot(request):
    # Fetch HIFI1 and ESR1 data
    hifi1 = Gene.objects.get(hugo_symbol="HIF1A")
    esr1 = Gene.objects.get(hugo_symbol="ESR1")

    # Fetch expressions
    hifi1_expressions = GeneExpression.objects.filter(gene=hifi1)
    esr1_expressions = GeneExpression.objects.filter(gene=esr1)

    # Create patient-wise mapping for matching
    hifi1_data = {exp.patient.patient_id: exp.z_score for exp in hifi1_expressions}
    esr1_data = {exp.patient.patient_id: exp.z_score for exp in esr1_expressions}

    # Match patients and create data
    common_patients = set(hifi1_data.keys()).intersection(esr1_data.keys())
    hifi1_values = [hifi1_data[pid] for pid in common_patients]
    esr1_values = [esr1_data[pid] for pid in common_patients]

    # Calculate Spearman and Pearson correlations
    spearman_corr, spearman_p = spearmanr(hifi1_values, esr1_values)
    pearson_corr, pearson_p = pearsonr(hifi1_values, esr1_values)

    # Perform linear regression
    slope, intercept, r_value, p_value, std_err = linregress(hifi1_values, esr1_values)

    # Create scatter plot
    fig = go.Figure()

    # Add scatter plot
    fig.add_trace(go.Scatter(
        x=hifi1_values,
        y=esr1_values,
        mode='markers',
        marker=dict(
            size=8,
            color='#5aacfd',
            line=dict(width=1, color='black'),
        ),
        name='Data Points'
    ))

    # Add regression line
    regression_line = [slope * x + intercept for x in hifi1_values]
    fig.add_trace(go.Scatter(
        x=hifi1_values,
        y=regression_line,
        mode='lines',
        line=dict(color='red'),
        name=f"y = {slope:.2f}x + {intercept:.2f}"
    ))

    # Update layout for full-width plot and legend on top
    fig.update_layout(
        title=dict(
            text="HIFI1 vs ESR1 mRNA Expression",
            x=0.5,
            xanchor='center',
            font=dict(size=20, color='#333')
        ),
        xaxis=dict(
            title="HIFI1: mRNA expression (log2 value + 1)",
            showline=True,
            linewidth=1,
            linecolor='black',
            mirror=True,
        ),
        yaxis=dict(
            title="ESR1: mRNA expression (log2 value + 1)",
            showline=True,
            linewidth=1,
            linecolor='black',
            mirror=True,
        ),
        legend=dict(
            orientation="h",  # Horizontal legend
            yanchor="bottom",  # Align to the bottom of the plot area
            y=1.05,  # Slightly above the plot
            xanchor="center",  # Center alignment
            x=0.5,  # Horizontal center
        ),
        plot_bgcolor='white',
        margin=dict(l=50, r=50, t=100, b=50),  # Adjust margins for better spacing
        height=500,  # Ensure the plot height fits well on smaller screens
    )

    # Convert Plotly graph to HTML
    plot_html = to_html(fig, full_html=False)

    # Pass the metrics and plot HTML to the template
    return render(request, 'gene_expression_plot.html', {
        'plot_html': plot_html,
        'spearman_corr': f"{spearman_corr:.2f} (p-value: {spearman_p:.2e})",
        'pearson_corr': f"{pearson_corr:.2f} (p-value: {pearson_p:.2e})",
        'r_squared': f"{r_value**2:.2f}",
        'regression_eq': f"y = {slope:.2f}x + {intercept:.2f}",
    })

# def gene_expression_plot(request):
#     # Fetch HIFI1 and ESR1 data
#     hifi1 = Gene.objects.get(hugo_symbol="HIF1A")
#     esr1 = Gene.objects.get(hugo_symbol="ESR1")

#     # Fetch expressions
#     hifi1_expressions = GeneExpression.objects.filter(gene=hifi1)
#     esr1_expressions = GeneExpression.objects.filter(gene=esr1)

#     # Create patient-wise mapping for matching
#     hifi1_data = {exp.patient.patient_id: exp.z_score for exp in hifi1_expressions}
#     esr1_data = {exp.patient.patient_id: exp.z_score for exp in esr1_expressions}

#     # Match patients and create data
#     common_patients = set(hifi1_data.keys()).intersection(esr1_data.keys())
#     hifi1_values = [hifi1_data[pid] for pid in common_patients]
#     esr1_values = [esr1_data[pid] for pid in common_patients]

#     # Calculate Spearman and Pearson correlations
#     spearman_corr, spearman_p = spearmanr(hifi1_values, esr1_values)
#     pearson_corr, pearson_p = pearsonr(hifi1_values, esr1_values)

#     # Perform linear regression
#     slope, intercept, r_value, p_value, std_err = linregress(hifi1_values, esr1_values)

#     # Create scatter plot
#     fig = go.Figure()

#     # Add scatter plot
#     fig.add_trace(go.Scatter(
#         x=hifi1_values,
#         y=esr1_values,
#         mode='markers',
#         marker=dict(
#             size=5, color='#5aacfd',line=dict(width=1, color='black'),
#         ),
#         name='Data Points'
#     ))

#     # Add regression line
#     regression_line = [slope * x + intercept for x in hifi1_values]
#     fig.add_trace(go.Scatter(
#         x=hifi1_values,
#         y=regression_line,
#         mode='lines',
#         line=dict(color='red'),
#         name=f"y = {slope:.2f}x + {intercept:.2f}"
#     ))

#     # Add annotations
#     fig.update_layout(
#         title=dict(
#             text="HIFI1 vs ESR1 mRNA Expression",
#             x=0.5,
#             xanchor='center',
#             font=dict(size=20, color='#333')
#         ),
#         xaxis_title="HIFI1: mRNA expression (log2 value + 1)",
#         yaxis_title="ESR1: mRNA expression (log2 value + 1)",
#         xaxis=dict(
#             showline=True,
#             linewidth=1,
#             linecolor='black',
#             mirror=True
#         ),
#         yaxis=dict(
#             showline=True,
#             linewidth=1,
#             linecolor='black',
#             mirror=True
#         ),
#         plot_bgcolor='white',
#     )

#     # Convert Plotly graph to HTML
#     plot_html = to_html(fig, full_html=False)

#     # Pass the metrics and plot HTML to the template
#     return render(request, 'gene_expression_plot.html', {
#         'plot_html': plot_html,
#         'spearman_corr': f"{spearman_corr:.2f} (p-value: {spearman_p:.2e})",
#         'pearson_corr': f"{pearson_corr:.2f} (p-value: {pearson_p:.2e})",
#         'r_squared': f"{r_value**2:.2f}",
#         'regression_eq': f"y = {slope:.2f}x + {intercept:.2f}",
#     })

import numpy as np
import plotly.graph_objects as go
from django.shortcuts import render
from tcgabcd.models import ClinicalSample
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from django.shortcuts import render
from tcgabcd.models import ClinicalSample
import pandas as pd

def tmb_race_plot(request):
    # Fetch data by linking ClinicalSample and ClinicalPatient through Patient
    samples = ClinicalSample.objects.filter(
        tmb_nonsynonymous__isnull=False,  # Exclude samples with missing TMB
        patient__clinical_data__race__isnull=False  # Exclude patients with missing race data
    ).select_related('patient__clinical_data')  # Optimize query with select_related

    # Prepare data for the plot
    data = [
        {
            "Race": sample.patient.clinical_data.race,  # Race from ClinicalPatient
            "TMB": sample.tmb_nonsynonymous,  # TMB from ClinicalSample
        }
        for sample in samples
    ]

    # Transform data into a pandas DataFrame
    df = pd.DataFrame(data)

    # Ensure data exists
    if df.empty:
        return render(request, "tmb_race_plot.html", {"plot_html": "<p>No data available to display.</p>"})

    # Apply log2 transformation to TMB values
    df["log2_TMB"] = np.log2(df["TMB"] + 1)  # Adding 1 to avoid log(0) issues

    # Ensure races are correctly mapped to unique numeric positions
    race_categories = sorted(df["Race"].unique())  # Sort for consistent ordering
    race_map = {race: i for i, race in enumerate(race_categories)}
    df["Race_numeric"] = df["Race"].map(race_map)  # Convert Race to numeric positions

    # Add jitter to Y-axis for cluster separation
    df["y_jitter"] = df["Race_numeric"] + np.random.uniform(-0.1, 0.1, len(df))  # Adjust jitter range

    # Create the figure
    fig = go.Figure()

    # Add boxplots for each race
    for race in df["Race"].unique():
        race_data = df[df["Race"] == race]
        fig.add_trace(go.Box(
            x=race_data["log2_TMB"],  # Use log2-transformed TMB for X-axis
            y=[race_map[race]] * len(race_data),  # Align with the respective category on Y-axis
            name=race,
            marker=dict(color="rgba(0,0,0,0)"),  # Transparent marker
            line=dict(color="gray"),  # Whiskers color
            fillcolor="rgba(190, 190, 190, 0.5)",  # Light gray fill for the box
            boxmean=True,  # Do not show mean line
            showlegend=False,  # Hide legend for box plot
            orientation="h",  # Horizontal orientation
            width=0.5  # Narrow width for better visualization
        ))
    # Add scatter points (strip plot) with adjusted Y-axis jitter and borders
    fig.add_trace(go.Scatter(
        x=df["log2_TMB"],  # Use log2-transformed TMB
        y=df["y_jitter"],  # Use jittered Y-axis
        mode="markers",
        marker=dict(
            size=10,
            color=df["TMB"],  # Color based on original TMB
            colorscale=[
                [0.0, "#fc8d59"],  # Light red for low values
                [0.2, "#d73027"],  # Faded red
                [0.5, "#d73027"],  # Medium red
                [1.0, "#7f0000"]   # Dark red for high values
            ],
            cmin=0,
            cmax=df["TMB"].max(),  # Automatically scale to the data range
            line=dict(
                width=1,  # Border thickness
                color="black"  # Border color
            ),
            showscale=True,
            colorbar=dict(
                title="TMB<br>(nonsynonymous)",
                titleside="right",
                x=1.15,  # Move the color bar away from the plot
                len=0.75,  # Adjust color bar height
                thickness=15,  # Adjust thickness
            ),
        ),
        name="TMB values"
    ))

    # Update layout to match the given style
    fig.update_layout(
        title=dict(
            text="TMB Score by Race Category",
            x=0.5,  # Center title
            font=dict(size=18)  # Larger title font
        ),
        xaxis=dict(
            title="TMB score (log2 value)",
            titlefont=dict(size=14),  # Larger font for the title
            tickfont=dict(size=12),  # Larger tick labels
            showgrid=True,  # Add grid lines
            gridcolor="lightgray",  # Subtle grid lines
            zerolinecolor="black",
        ),
        yaxis=dict(
            title="Race category",
            titlefont=dict(size=14),  # Larger font for the title
            tickvals=list(race_map.values()),  # Map numeric positions back to race names
            ticktext=list(race_map.keys()),
            tickfont=dict(size=12),  # Larger tick labels
            showgrid=False,  # Keep Y-axis clean
        ),
        height=700,  # Increase plot height
        width=1000,  # Increase plot width
        template="plotly_white",
    )

    fig.data[1].update(marker=dict(size=12))  # Enlarge scatter points
    for trace in fig.data:
        if isinstance(trace, go.Box):
            trace.update(width=0.4)  # Reduce box plot width

    # Convert the plot to HTML
    plot_html = fig.to_html(full_html=False)

    # Pass the plot to the template
    return render(request, "tmb_race_plot.html", {"plot_html": plot_html})
