import os
import math
import datetime
import threading
import urllib.request
import ssl
import numpy as np
import pytz

import customtkinter as ctk
from tkinter import messagebox

import ephem
from skyfield.api import Loader

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# ==========================================
# FUNGSI UTILITAS & UNDUHAN
# ==========================================
def download_custom_bsp(filename, url):
    filepath = os.path.join(BASE_DIR, filename)
    if not os.path.exists(filepath):
        try:
            req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
            with urllib.request.urlopen(req) as response, open(filepath, 'wb') as out_file:
                out_file.write(response.read())
        except Exception as e:
            print(f"Gagal mengunduh {filename}: {e}")

# ==========================================
# DATABASE KOTA (Dari Master Code)
# ==========================================
CITY_DB = {
    "Aceh": {"Banda Aceh": (5.5483, 95.3238), "Sabang": (5.8942, 95.3184), "Lhokseumawe": (5.1801, 97.1507)},
    "Jawa Tengah": {"Semarang": (-7.0667, 110.4100), "Surakarta": (-7.5703, 110.8297)},
    "DKI Jakarta": {"Jakarta Pusat": (-6.1865, 106.8270)},
    "Jawa Timur": {"Surabaya": (-7.2575, 112.7521), "Malang": (-7.9797, 112.6304)},
    "Jawa Barat": {"Bandung": (-6.9175, 107.6191)},
    "Sumatera Barat": {"Padang": (-0.9471, 100.4172)},
    "Papua": {"Jayapura": (-2.5916, 140.6690)},
    "Papua Barat Daya": {"Sorong": (-0.8765, 131.2558)},
    "Sulawesi Selatan": {"Makassar": (-5.1476, 119.4327)},
    "Kalimantan Timur": {"Balikpapan": (-1.2654, 116.8312)},
    "Selandia Baru": {"Gisborne": (-38.6623, 178.0176), "Wellington": (-41.2865, 174.7762), "Waitangi": (-35.2681, 174.0803)},
    "Australia": {"Sydney": (-33.8688, 151.2093), "Perth": (-31.9505, 115.8605)},
    "Jepang": {"Tokyo": (35.6762, 139.6503)},
    "China": {"Beijing": (39.9042, 116.4074), "Kashgar": (39.4677, 75.9938)},
    "Asia Selatan & Timteng": {
        "New Delhi": (28.6139, 77.2090), "Karachi": (24.8607, 67.0011), 
        "Tehran": (35.6892, 51.3890), "Kabul": (34.5553, 69.2075)
    },
    "Arab Saudi": {"Makkah": (21.4225, 39.8262), "Riyadh": (24.7136, 46.6753)},
    "Mesir": {"Kairo": (30.0444, 31.2357)},
    "Maroko": {"Rabat": (34.0209, -6.8416)},
    "Turki": {"Istanbul": (41.0082, 28.9784)},
    "Eropa Daratan": {"Paris": (48.8566, 2.3522), "Berlin": (52.5200, 13.4050), "Roma": (41.9028, 12.4964)},
    "Inggris Raya": {"London": (51.5074, -0.1278)},
    "Rusia": {"Moskow": (55.7558, 37.6173), "Vladivostok": (43.1198, 131.8869)},
    "Afrika Selatan": {"Cape Town": (-33.9249, 18.4241)},
    "Afrika Barat": {"Dakar": (14.7167, -17.4677), "Lagos": (6.5244, 3.3792)},
    "Amerika Serikat": {
        "New York City": (40.7128, -74.0060), "Los Angeles": (34.0522, -118.2437), 
        "Anchorage": (61.2181, -149.9003)
    },
    "Amerika Selatan": {
        "Lima": (-12.0464, -77.0428), "Santiago": (-33.4489, -70.6693),
        "Sao Paulo": (-23.5505, -46.6333), "Buenos Aires": (-34.6037, -58.3816)
    },
    "Kepulauan Pasifik (Timur Jauh)": {"Kiritimati": (1.8709, -157.3962), "Apia": (-13.8333, -171.7667)}
}

# ==========================================
# KELAS UTAMA APLIKASI (MODUL 3)
# ==========================================
class Menu3App(ctk.CTk):
    def __init__(self):
        super().__init__()

        self.title("Analisis Hilal Global (Modul 3)")
        self.geometry("1150x700")
        self.minsize(900, 600)

        try: _create_unverified_https_context = ssl._create_unverified_context
        except AttributeError: pass
        else: ssl._create_default_https_context = _create_unverified_https_context

        ctk.set_appearance_mode("Dark")
        ctk.set_default_color_theme("blue")

        # Inisialisasi Skyfield
        self.load_obj = Loader(BASE_DIR, verbose=False)
        self.ts = self.load_obj.timescale()
        self.eph = None
        self.ephemeris_name = "de421.bsp"

        self.setup_ui()
        threading.Thread(target=self.load_ephemeris, daemon=True).start()

    def get_header(self, width):
        lines = [
            "By the Name of Allah",
            "KALENDER HIJRIAH GLOBAL TUNGGAL",
            "KHGT Times 7.2 - Global Hilal Analyzer"
        ]
        return "\n".join(line.center(width) for line in lines)

    def load_ephemeris(self):
        try:
            self.lbl_status.configure(text=f"Mengunduh/Memuat ephemeris ({self.ephemeris_name})...", text_color="#00E5FF")
            download_custom_bsp(self.ephemeris_name, f"https://hisabmu.com/aifikih/{self.ephemeris_name}")
            self.eph = self.load_obj(self.ephemeris_name)
            self.lbl_status.configure(text=f"Sistem Siap ({self.ephemeris_name} Dimuat)", text_color="#00E676")
            self.btn_hitung.configure(state="normal")
        except Exception as e:
            self.lbl_status.configure(text="Gagal memuat ephemeris!", text_color="#FF1744")
            messagebox.showerror("Error", f"Gagal memuat file ephemeris.\nDetail: {str(e)}")

    def auto_switch_ephemeris(self, target_year):
        ephemeris_priority = [
            ("de421.bsp", 1900, 2050),
            ("de442.bsp", 1550, 2650),
            ("de406.bsp", -3000, 3000)
        ]

        best_bsp = None
        for filename, min_yr, max_yr in ephemeris_priority:
            if min_yr <= target_year <= max_yr and os.path.exists(os.path.join(BASE_DIR, filename)):
                best_bsp = filename
                break

        if best_bsp is None:
            for filename, _, _ in ephemeris_priority:
                if os.path.exists(os.path.join(BASE_DIR, filename)):
                    best_bsp = filename
                    break

        if best_bsp is None:
            best_bsp = "de421.bsp"

        if self.ephemeris_name != best_bsp:
            try:
                self.eph = self.load_obj(best_bsp)
                self.ephemeris_name = best_bsp
                print(f"[Engine] Berhasil beralih ke ephemeris {best_bsp} untuk tahun {target_year}")
            except Exception as e:
                raise RuntimeError(str(e))

    def setup_ui(self):
        self.grid_columnconfigure(0, weight=0) 
        self.grid_columnconfigure(1, weight=1) 
        self.grid_rowconfigure(0, weight=1)

        now = datetime.datetime.now()
        curr_y, curr_m, curr_d = str(now.year), f"{now.month:02d}", f"{now.day:02d}"

        # ================= SIDEBAR KIRI =================
        self.sidebar = ctk.CTkScrollableFrame(self, width=320, corner_radius=0, fg_color="#181818")
        self.sidebar.grid(row=0, column=0, sticky="nsew")

        ctk.CTkLabel(self.sidebar, text="GLOBAL ANALYZER", font=("Segoe UI", 24, "bold"), text_color="#00E5FF").pack(pady=(20, 15))

        # 1. Tanggal Referensi UTC
        frame_gha_date = ctk.CTkFrame(self.sidebar, fg_color="#212121")
        frame_gha_date.pack(fill="x", padx=15, pady=5)
        ctk.CTkLabel(frame_gha_date, text="TANGGAL REFERENSI (UTC)", font=("Segoe UI", 12, "bold")).pack(anchor="w", padx=10, pady=(8, 2))
        
        gha_date_grid = ctk.CTkFrame(frame_gha_date, fg_color="transparent")
        gha_date_grid.pack(fill="x", padx=10, pady=(0, 5))

        ctk.CTkLabel(gha_date_grid, text="dd", font=("Segoe UI", 10), text_color="#9E9E9E").grid(row=0, column=0, pady=(0, 2))
        ctk.CTkLabel(gha_date_grid, text="mm", font=("Segoe UI", 10), text_color="#9E9E9E").grid(row=0, column=1, pady=(0, 2))
        ctk.CTkLabel(gha_date_grid, text="yyyy", font=("Segoe UI", 10), text_color="#9E9E9E").grid(row=0, column=2, pady=(0, 2))

        self.entry_gha_day = ctk.CTkEntry(gha_date_grid, width=45, justify="center")
        self.entry_gha_day.insert(0, curr_d)
        self.entry_gha_day.grid(row=1, column=0, padx=(0, 5))
        
        self.entry_gha_month = ctk.CTkEntry(gha_date_grid, width=45, justify="center")
        self.entry_gha_month.insert(0, curr_m)
        self.entry_gha_month.grid(row=1, column=1, padx=5)
        
        self.entry_gha_year = ctk.CTkEntry(gha_date_grid, width=70, justify="center")
        self.entry_gha_year.insert(0, curr_y)
        self.entry_gha_year.grid(row=1, column=2, padx=(5, 0))
        
        ctk.CTkLabel(frame_gha_date, text="hh:mm:ss", font=("Segoe UI", 10), text_color="#9E9E9E").pack(anchor="w", padx=12, pady=(5, 0))
        
        self.entry_gha_time = ctk.CTkEntry(frame_gha_date, placeholder_text="Waktu HH:MM:SS (Def: 12:00:00)")
        self.entry_gha_time.insert(0, "12:00:00")
        self.entry_gha_time.pack(fill="x", padx=10, pady=(0, 10))

        # 2. Tombol Proses
        self.btn_hitung = ctk.CTkButton(self.sidebar, text="▶ PROSES DATA", font=("Segoe UI", 14, "bold"), height=45, fg_color="#1565C0", hover_color="#0D47A1", command=self.run_calculation, state="disabled")
        self.btn_hitung.pack(fill="x", padx=15, pady=(20, 10))

        self.lbl_status = ctk.CTkLabel(self.sidebar, text="Menyiapkan Sistem...", font=("Consolas", 11), text_color="#FFAB40")
        self.lbl_status.pack(pady=5)

        # ================= MAIN AREA KANAN =================
        self.main_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.main_frame.grid(row=0, column=1, sticky="nsew", padx=20, pady=20)
        self.main_frame.grid_rowconfigure(1, weight=1)
        self.main_frame.grid_columnconfigure(0, weight=1)

        self.lbl_main_title = ctk.CTkLabel(self.main_frame, text="Global Hilal Visibility Analyzer (Iterasi Ephem)", font=("Segoe UI", 20, "bold"))
        self.lbl_main_title.grid(row=0, column=0, sticky="w", pady=(0, 10))

        self.textbox = ctk.CTkTextbox(self.main_frame, font=("Consolas", 13), fg_color="#101010", text_color="#00E676", wrap="none")
        self.textbox.grid(row=1, column=0, sticky="nsew")
        self.textbox.insert("1.0", "Silakan klik 'PROSES DATA' untuk memulai pemindaian global ke ratusan kota di seluruh benua.\n\nSistem akan mengurutkan hasilnya berdasarkan abjad negara dan kota secara otomatis.")
        self.textbox.configure(state="disabled")

    def run_calculation(self):
        self.lbl_status.configure(text="Memindai Data Global...", text_color="#FFAB40")
        self.btn_hitung.configure(state="disabled")
        self.textbox.configure(state="normal")
        self.textbox.delete("1.0", "end")
        self.textbox.insert("1.0", "Mengeksekusi iterasi Ephemeris ke seluruh koordinat dunia...\n")
        self.textbox.configure(state="disabled")
        
        threading.Thread(target=self.calculate_global_hilal, daemon=True).start()

    def calculate_global_hilal(self):
        try:
            y = int(self.entry_gha_year.get())
            m = int(self.entry_gha_month.get())
            d = int(self.entry_gha_day.get())
            time_str = self.entry_gha_time.get()
            if not time_str: time_str = "12:00:00"
            
            # Pastikan Ephemeris ter-load sesuai tahun yang diminta
            self.auto_switch_ephemeris(y)

            tanggal_referensi_utc = f"{y}/{m}/{d} {time_str}"
            
            matahari = ephem.Sun()
            bulan = ephem.Moon()
            
            waktu_ijtimak = ephem.previous_new_moon(tanggal_referensi_utc)
            str_ijtimak = str(ephem.Date(waktu_ijtimak))
            
            output_lines = []
            header = f"{self.get_header(138)}\n"
            header += f"{'[ Analisis Hilal Global (KHGT, MABIMS, Wujudul Hilal) - Ephem ]'.center(138)}\n\n"
            header += f"* Tanggal Referensi Pencarian (UTC): {tanggal_referensi_utc}\n"
            header += f"* Waktu Ijtimak/Konjungsi Terdekat (UTC): {str_ijtimak}\n"
            header += f"* Catatan: Diurutkan Berdasarkan Abjad Negara, lalu Abjad Kota.\n"
            header += "="*138 + "\n"
            header += f"{'Negara':<15} | {'Kota':<14} | {'Sunset (UTC)':<16} | {'Umur Bln':<9} | {'Alt(Geo)':<8} | {'Eln(Geo)':<8} | {'Alt(Top)':<8} | {'Eln(Top)':<8} | Status\n"
            header += "-"*138
            output_lines.append(header)
            
            hasil_kalkulasi = []
            
            for negara, kota_dict in CITY_DB.items():
                for nama_kota, koordinat in kota_dict.items():
                    lintang, bujur = koordinat
                    pengamat = ephem.Observer()
                    pengamat.lat = math.radians(lintang)
                    pengamat.lon = math.radians(bujur)
                    pengamat.elevation = 0 
                    pengamat.pressure = 1010 
                    pengamat.temp = 25
                    
                    try:
                        pengamat.date = ephem.Date(ephem.Date(tanggal_referensi_utc) - 0.5) 
                        waktu_sunset = pengamat.next_setting(matahari)
                    except (ephem.AlwaysUpError, ephem.NeverUpError):
                        hasil_kalkulasi.append({
                            'negara': negara,       
                            'kota': nama_kota,      
                            'waktu_sort': 999999.0, 
                            'baris_teks': f"{negara[:15]:<15} | {nama_kota[:14]:<14} | {'Anomali Ekstrem':<16} | {'-':<9} | {'-':<8} | {'-':<8} | {'-':<8} | {'-':<8} | Lintang Tinggi (Midnight Sun)"
                        })
                        continue
                        
                    pengamat.date = waktu_sunset
                    matahari.compute(pengamat)
                    bulan.compute(pengamat)
                    
                    # 1. Parameter Toposentris (Bawaan Ephem)
                    alt_topo = math.degrees(bulan.alt)
                    elong_topo = math.degrees(ephem.separation(matahari, bulan))
                    
                    # 2. Parameter Geosentris (DISELELARASKAN DENGAN SKYFIELD)
                    t_sunset_sky = self.ts.from_datetime(waktu_sunset.datetime().replace(tzinfo=pytz.utc))
                    obs_center = self.eph['earth'].at(t_sunset_sky)
                    s_app_sky = obs_center.observe(self.eph['sun']).apparent()
                    m_app_sky = obs_center.observe(self.eph['moon']).apparent()

                    elong_geo = s_app_sky.separation_from(m_app_sky).degrees

                    gmst = t_sunset_sky.gast
                    lst_deg = (gmst * 15.0) + bujur
                    ra_m, dec_m, _ = m_app_sky.radec(epoch=t_sunset_sky)
                    
                    ha_deg = lst_deg - (ra_m.hours * 15.0)
                    ha_rad = math.radians(ha_deg)
                    lat_rad = math.radians(lintang)
                    d_rad = dec_m.radians
                    
                    sin_alt_geo = math.sin(d_rad) * math.sin(lat_rad) + math.cos(d_rad) * math.cos(lat_rad) * math.cos(ha_rad)
                    alt_geo = math.degrees(math.asin(max(-1.0, min(1.0, sin_alt_geo))))
                    
                    umur_bulan_desimal = (waktu_sunset - waktu_ijtimak) * 24
                    
                    if umur_bulan_desimal < 0:
                        format_umur = "B. Ijtimak"
                        status_visibilitas = "Negatif (Bulan Tua)"
                    else:
                        jam = int(umur_bulan_desimal)
                        menit = int((umur_bulan_desimal - jam) * 60)
                        format_umur = f"{jam}j {menit}m"
                        
                        # EVALUASI STATUS BERJENJANG
                        if alt_geo >= 5.0 and elong_geo >= 8.0:
                            status_visibilitas = "Memenuhi Kriteria KHGT"
                        elif alt_topo >= 3.0 and elong_topo >= 6.4:
                            status_visibilitas = "Memenuhi Kriteria MABIMS"
                        elif alt_geo > 0 or alt_topo > 0:
                            status_visibilitas = "Wujudul Hilal"
                        else:
                            status_visibilitas = "Negatif (Bawah Ufuk)"
                    
                    alt_g_str = f"{alt_geo:.2f}°"
                    elong_g_str = f"{elong_geo:.2f}°"
                    alt_t_str = f"{alt_topo:.2f}°"
                    elong_t_str = f"{elong_topo:.2f}°"
                    sunset_str = str(ephem.Date(waktu_sunset))
                    
                    baris_teks = f"{negara[:15]:<15} | {nama_kota[:14]:<14} | {sunset_str[:16]:<16} | {format_umur[:9]:<9} | {alt_g_str[:8]:<8} | {elong_g_str[:8]:<8} | {alt_t_str[:8]:<8} | {elong_t_str[:8]:<8} | {status_visibilitas}"
                    
                    hasil_kalkulasi.append({
                        'negara': negara,       
                        'kota': nama_kota,      
                        'waktu_sort': waktu_sunset, 
                        'baris_teks': baris_teks
                    })
            
            # Sorting berdasarkan abjad Negara dan Kota
            hasil_kalkulasi.sort(key=lambda x: (x['negara'].lower(), x['kota'].lower()))
            
            for hasil in hasil_kalkulasi:
                output_lines.append(hasil['baris_teks'])
                    
            output_lines.append("="*138)
            self.after(0, self.display_result, "\n".join(output_lines))
            
        except Exception as e:
            import traceback
            self.after(0, self.display_error, f"{str(e)}\n\n{traceback.format_exc()}")

    def display_result(self, report_text):
        self.textbox.configure(state="normal")
        self.textbox.delete("1.0", "end")
        self.textbox.insert("1.0", report_text)
        self.textbox.configure(state="disabled")
        self.lbl_status.configure(text="Kalkulasi Selesai", text_color="#00E676")
        self.btn_hitung.configure(state="normal")

    def display_error(self, error_msg):
        self.textbox.configure(state="normal")
        self.textbox.delete("1.0", "end")
        self.textbox.insert("1.0", f"TERJADI KESALAHAN:\n{error_msg}")
        self.textbox.configure(state="disabled")
        self.lbl_status.configure(text="Error Kalkulasi", text_color="#FF1744")
        self.btn_hitung.configure(state="normal")

if __name__ == "__main__":
    app = Menu3App()
    app.mainloop()