#!/usr/bin/env python3
"""Scatter plot visualization module for observational data."""

import sqlite3
import datetime
from statistics import median
from typing import List, Optional, Tuple

import numpy as np
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.colorbar as cbar
import matplotlib.cm as cm
import matplotlib.colors as colors
from matplotlib.colors import LinearSegmentedColormap
from matplotlib.ticker import FuncFormatter
from mpl_toolkits.axes_grid1 import make_axes_locatable
import cartopy.feature

import pikobs
def projectPpoly(proj, lat, lon, deltax, deltay, src_crs):
    """Transform polygon corners from lat/lon to projection coordinates."""
    points = []
    for dlon, dlat in [(-deltax, -deltay), (-deltax, deltay), (deltax, deltay), (deltax, -deltay)]:
        x, y = proj.transform_point(lon + dlon, lat + dlat, src_crs)
        points.append([x, y])
    return points
def surface_area_lat_lon(lat1, lat2, lon1, lon2):
    """Calculate surface area in km² for a lat/lon rectangle.
    
    Formula: (π/180)R² |sin(lat2)-sin(lat1)| |lon2-lon1|
    Uses numpy arrays for vectorized computation.
    """
    R = 6371.0  # Earth radius in km
    lat2 = np.minimum(lat2, 90.0)
    surf = (R**2 * (np.pi / 180.0) * 
            np.abs(np.sin(lat2 * np.pi / 180.0) - np.sin(lat1 * np.pi / 180.0)) * 
            np.abs(lon2 - lon1))
    return surf
def days_between(d1, d2):
    """Calculate days between two dates in format YYYYMMDDHH."""
    d1_dt = datetime.datetime.strptime(d1, "%Y%m%d%H")
    d2_dt = datetime.datetime.strptime(d2, "%Y%m%d%H")
    delta = d2_dt - d1_dt
    return delta.total_seconds() / (24 * 3600)

def round_first_digit_3(x):
    """Format number with adaptive decimal places based on magnitude."""
    if x == 0:
        return "0"
    abs_x = abs(x)
    if abs_x >= 10:
        return f"{int(x)}"
    elif abs_x >= 0.1:
        return f"{round(x, 2)}"
    else:
        mantissa, exp = f"{x:.1e}".split("e")
        mantissa = mantissa.split(".")[0]
        return f"{mantissa}e{int(exp)}"

WIND_TYPE_MAPPING = {
    1: "Wind derived from cloud motion observed in the infrared channel",
    2: "Wind derived from cloud motion observed in the visible channel",
    3: "Wind derived from cloud motion observed in the water vapour channel",
    4: "Wind derived from motion observed in a combination of spectral channels",
    5: "Wind derived from motion observed in the water vapour channel in clear air",
    6: "Wind derived from motion observed in the ozone channel",
    7: "Wind derived from motion observed in water vapour channel (cloud or clear air not specified)",
}


def _build_where_clause(id_stn: str, vcoord: str) -> str:
    """Build SQL WHERE clause based on id_stn and vcoord parameters.
    
    Args:
        id_stn (str): Station ID ('join', 'all', or specific ID)
        vcoord (str): Vertical coordinate ('join' or specific value)
    
    Returns:
        str: WHERE clause fragment
    """
    if id_stn == 'join' and vcoord == 'join':
        return ""
    elif id_stn == 'join' and vcoord != 'join':
        return f" and vcoord = {vcoord}"
    elif id_stn != 'join' and vcoord == 'join':
        return f" and id_stn = '{id_stn}'"
    else:  # Both id_stn and vcoord are specific
        return f" and vcoord = {vcoord} and id_stn = '{id_stn}'"


def wind_type(code_or_codes):
    """Get wind type description(s) for given code(s)."""
    def map_one(c):
        try:
            return WIND_TYPE_MAPPING.get(int(c), f"Unknown code {c}")
        except (ValueError, TypeError):
            return f"Unknown code {c}"

    if isinstance(code_or_codes, (list, tuple, set)):
        return [map_one(c) for c in code_or_codes]

    try:
        import pandas as pd
        if isinstance(code_or_codes, pd.Series):
            return code_or_codes.map(map_one)
    except ImportError:
        pass

    return map_one(code_or_codes)
import sqlite3
import matplotlib.pyplot as plt
import os
from typing import List
import numpy as np

import matplotlib.pyplot as plt
import numpy as np
import os
import sqlite3
from typing import List

import sqlite3
import matplotlib.pyplot as plt
import numpy as np
import os
from typing import List


import sqlite3
import matplotlib.pyplot as plt
import numpy as np
import os
from typing import List
import os
import sqlite3
import numpy as np
import matplotlib.pyplot as plt
from typing import List

# Mathematical function for Probability Density
def calculate_gaussian_pdf(x, mu, sigma):
    return (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mu) / sigma) ** 2)

def histogram_plot(
    region: str,
    family: str,
    id_stn: str,
    datestart: str,
    dateend: str,
    pathwork: str,
    flag_criteria: str,
    vcoord: str,
    filesin: List[str],
    namesin: List[str],
    varno: int
) -> None:
    """
    Generates a professional OMP Residual Histogram with a fitted Gaussian.
    Gaussian curves are scaled by Peak Amplitude to physically touch the highest histogram bar.
    """
    # --- Visual Style Setup ---
    plt.rcParams.update({'font.size': 10, 'figure.dpi': 120})
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.grid(axis='y', linestyle='--', alpha=0.3, zorder=0)

    conn = sqlite3.connect(":memory:")
    cursor = conn.cursor()

    def get_moyenne_data(filepath, alias):
        try:
            cursor.execute(f"ATTACH DATABASE '{filepath}' AS {alias}")
            query = f"""
                SELECT bin_range, omp_frequency 
                FROM {alias}.moyenne 
                WHERE varno = {varno} AND id_stn = '{id_stn}'
                ORDER BY bin_range
            """
            cursor.execute(query)
            return cursor.fetchall()
        except Exception as e:
            print(f"[-] Error attaching/querying {alias} ({filepath}): {e}")
            return []

    # --- Data Processing & Pairing ---
    experiments = []
    for i, fpath in enumerate(filesin):
        name = namesin[i] if i < len(namesin) and namesin[i] != "undefined" else f"Experiment {i+1}"
        experiments.append((fpath, name))

    valid_results = []
    colors = ['#4A90E2', '#FF6B6B', '#50E3C2', '#F5A623'] 
    
    for i, (fpath, exp_name) in enumerate(experiments):
        alias = f"db{i+1}"
        data = get_moyenne_data(fpath, alias)
        if data:
            bins = np.array([r[0] for r in data])
            counts = np.array([r[1] for r in data])
            
            if np.sum(counts) > 0:
                valid_results.append({
                    'name': exp_name, 
                    'bins': bins, 
                    'counts': counts, 
                    'color': colors[len(valid_results) % len(colors)]
                })
        else:
            print(f"[!] Warning: No data found for {exp_name} on channel {varno}.")

    if not valid_results:
        print(f"[-] Aborting: No data for Plot {id_stn} Channel {varno}")
        conn.close()
        return

    # --- Plotting Raw Data and Gaussian Fits ---
    for res in valid_results:
        bins = res['bins']
        counts = res['counts']
        name = res['name']
        color = res['color']
        
        N = np.sum(counts)

        # 1. Step Plot (Background Histogram)
        ax.step(bins, counts, where='mid', color=color, alpha=0.6, linewidth=1.5, zorder=2)
        ax.fill_between(bins, counts, step="mid", alpha=0.3, color=color, zorder=1)

        # 2. Gaussian Fit (Foreground) scaled by PEAK AMPLITUDE
        if len(bins) > 1:
            mu = np.average(bins, weights=counts)
            variance = np.average((bins - mu)**2, weights=counts)
            sigma = np.sqrt(variance)

            # Generamos curva suave
            x_smooth = np.linspace(bins.min(), bins.max(), 500)
            pdf_smooth = calculate_gaussian_pdf(x_smooth, mu, sigma)
            
            # --- MAGIA DE ESCALADO POR PICO ---
            # Encontramos la barra más alta del histograma
            max_histogram_height = np.max(counts)
            # Encontramos el punto más alto de la curva matemática
            max_pdf_height = np.max(pdf_smooth)
            
            # Multiplicamos la curva matemática por la diferencia proporcional
            y_smooth = pdf_smooth * (max_histogram_height / max_pdf_height)
            # ----------------------------------

            label = f"OMP - {name} (N={int(N)}, $\mu={mu:.2f}$, $\sigma={sigma:.2f}$)"
            ax.plot(x_smooth, y_smooth, color=color, linewidth=2.5, label=label, zorder=4)

    # --- Scientific Reference Elements ---
    ax.axvline(0, color='#333333', linestyle='-', linewidth=1.2, alpha=0.7, zorder=5)
    ax.set_xlim(-10, 10) 

    # --- Labels and Aesthetics ---
    ax.set_title(f"OMP Residual Distribution • {id_stn}\nFamily: {family} | Varno: {varno} | Region: {region}", 
                 fontsize=13, fontweight='bold', pad=15, color='#2C3E50')
    
    ax.set_xlabel("Residual (Observation - Prediction)", fontsize=11)
    ax.set_ylabel("Frequency (Counts)", fontsize=11) 
    
    # Full Bounding Box: 4 dark lines
    for spine in ax.spines.values():
        spine.set_visible(True)
        spine.set_color('#333333')
        spine.set_linewidth(1.2)
    
    # Legend Outside at the Bottom
    ax.legend(
        loc='upper center', 
        bbox_to_anchor=(0.5, -0.15),
        ncol=2,
        frameon=True, 
        facecolor='white', 
        framealpha=1.0, 
        edgecolor='#ECECEC'
    )

    # --- Output ---
    filename = f"hist_{id_stn}_{varno}_{flag_criteria}_{family}_{region}_{vcoord}_{datestart}.png"
    save_path = os.path.join(pathwork, family, filename)
    os.makedirs(os.path.dirname(save_path), exist_ok=True)
    
    plt.savefig(save_path, bbox_inches='tight', facecolor='white')
    plt.close()
    
    print(f"[OK] Histogram with Peak-Scaled Gaussian saved: {filename}")
    conn.close()
