import os
import bpy
import platform
import subprocess
import sys
import shutil
import re

from pathlib import Path
from datetime import datetime

from . import save_translation
from .save_translation import SaveTranslation

system = platform.system()
ADDON_ID = __package__.rsplit(".", 1)[0]

class BaseType:
    CORVETTE = "PlayerShipBase"
    BASE = "HomePlanetBase"
    FREIGHTER = "FreighterBase"
    EXTERNAL_BASE = "ExternalPlanetBase"
    
class BaseData:
    
    base_index: int
    base_name: str
    user_data: str
    galactic_address: str
    base_type: int
    parts_count: int
    save_slot = []
    
    
    def __init__(self, index = None ,base = None, ship_ownership = None):
        
        if index is None or base is None:
            return
        
        in_base_type = base[SaveTranslation.base_type][SaveTranslation.persistent_base_types]
        parts_count = len(base[SaveTranslation.objects])
        base_name = base[SaveTranslation.base_name]
        user_data = base[SaveTranslation.user_data]
        
        if len(base_name.strip()) == 0:
            base_name = "unknow_name"
            
        ship_ownership_entry = ship_ownership[user_data] if in_base_type == BaseType.CORVETTE else None
        if ship_ownership_entry is not None and in_base_type == BaseType.CORVETTE and base_name == "Default":
            base_name = ship_ownership_entry[SaveTranslation.base_name]
        
        self.base_index = index
        self.base_name = base_name
        self.user_data = user_data
        self.galactic_address = str(base[SaveTranslation.galactic_address])
        self.base_type = in_base_type
        self.parts_count = parts_count
        
        
# called to display messages/notificatoin
def ShowMessageBox(message="", title="Message Box", icon="INFO"):
    def draw(self, context):
        self.layout.label(text=message)
    bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)
    
# return save folder path for different OS versions
def get_default_save_folder():
    if system == "Windows":
        # windows
        return (Path.home()/ "AppData/Roaming/HelloGames/NMS" )
    elif system == "Darwin":
        # macOS
        return (Path.home()/ "Library/Application Support/HelloGames/NMS" )
    elif system == "Linux":
        # Proton / Steam Play
        return (Path.home() / ".steam/steam/steamapps/compatdata" )
    else:
        ShowMessageBox(message="Manually select save folder", title= "Default sve folder not found")
        return Path.home()
   
# return parth to file generated by steam that contains persona names of each account logged into system
def get_steamname_path():
    if system == "Windows": 
        # windows
        return (Path("C:/Program Files (x86)/Steam/config/loginusers.vdf"))
    elif system == "Darwin":  
        # macOS
        return ( Path.home() / "Library/Application Support/Steam/config/loginusers.vdf")
    elif system == "Linux":
        # Linux, most common Steam install location
        return ( Path.home()/ ".steam/steam/config/loginusers.vdf")
    else:
        return None
    
# extract steam persona names from loginusers.vdf file and return dic of "steam_id":"persona name"
def get_steam_names():
    # it is stored in format like ..."users: { "steam_id" {"PersonaName" "name"} }
    pattern = re.compile(
        r'"(\d+)"\s*{[^}]*"PersonaName"\s*"([^"]+)"',
        re.DOTALL
    )
    steam_names = {}
    steamname_path = get_steamname_path()
    if steamname_path.exists():
        text = steamname_path.read_text(encoding="utf-8")
        for steam_id, persona_name in pattern.findall(text):
            steam_names[str(steam_id)] = persona_name
    return steam_names

#returns list of files with valid save file names within a given folder, parameter is string
def get_hg_files_in_folder(folder):
    hg_files_list = []
    for file in Path(folder).glob("save*.hg"):
        if re.fullmatch(r"save(\d+)?\.hg", file.name):
            hg_files_list.append(file)
    return hg_files_list

#Returns all account folders
def get_accounts_list(context):
    prefs = context.preferences.addons[ADDON_ID].preferences
    save_folder_path = prefs.nms_save_folder_path
    
    steam_names = get_steam_names()
    accounts_list = []
    root_dir = Path(save_folder_path)
    for folder in root_dir.iterdir():
        if not folder.is_dir():
            continue
        # Steam/Gamepass account folders
        if folder.name.startswith("st_"):
            files_list = get_hg_files_in_folder(str(folder))
            account_number = folder.name[3:]
            steam_persona = steam_names.get(account_number,None)
            if files_list is not None:
                if len(files_list) > 0:
                    accounts_list.append({
                        "steam_persona" : steam_persona,
                        "folder"  : folder
                    })
    return accounts_list
    

# Returns list of save slot data that contains save name and save files lines
def get_save_slots_list(account):
    from .save_file import SaveFile
    
    # to validate correct save file name
    pattern = re.compile(r"save(\d+)\.hg")
    # store list of all hg save files
    hg_files_list = get_hg_files_in_folder(account)
    
    # interate through each save file and record their pairs
    save_slots = []
    for save_2 in hg_files_list:
        
        # save type is "Main" for normal save and "Season" for expeditiion
        try:
            save_type = SaveFile(save_2).search_property(SaveTranslation.active_context)
            if save_type is None:
                continue
        except:
            continue
        
        # validate name of save file
        match = pattern.fullmatch(save_2.name)
        if not match:
            continue
        
        #extract number from name of save file
        file_number = int(match.group(1))
        if file_number %2 == 1:
            continue
        
        #name of linked save file
        save_1_name = "save.hg" if file_number == 2 else f"save{file_number - 1}.hg"
        save_1 = Path(save_2.parent / save_1_name )
        #check if linked save file exits in save folder or not
        save_1_found = next((p for p in hg_files_list if p == save_1), None)
        if not save_1_found:
            continue
        
        #slot number is always half of second save file's number
        save_slot_number = file_number//2
        #links to save files for this slot
        saves_links = [str(save_1), str(save_2)]
        #extract save's name from save file data by partially loading it, and increasing efficiency
        save_name = SaveFile(save_2).search_property(SaveTranslation.save_name) if save_type == "Main" else "Expedition"
        
        save_slot = {
            "slot": save_slot_number,
            "saves": saves_links,
            "save_name": save_name or "Un-named"
        }
        save_slots.append(save_slot)
        
    #sort list according to slot number
    save_slots.sort(key=lambda x: x["slot"])
    return save_slots  

# returns list of data related to bses present in a save slot
def extract_bases_list_from_save(save_slot):
    save_file = get_save_file(save_slot)
    data = save_file.load()
    
    obfuscated_persistent_base_data = save_file.get_bases_list_pointer()
    ship_ownsership = save_file.get_ship_ownership_pointer()
    freighter_name = save_file.get_freighter_name()
    
    bases = []
    corvettes = []
    external = []
    freighter = None
    
    total_parts_count = 0
    
    #record only what is necessary rather than entire data about base
    for index,base in enumerate(obfuscated_persistent_base_data):
        
        base_data = BaseData(index, base, ship_ownsership)
        base_data.save_slot = save_slot
        
        #store list of corvettes and bases in their respective list
        match base_data.base_type :
            case BaseType.CORVETTE:
                corvettes.append(base_data)
            case BaseType.BASE:
                bases.append(base_data)
            case BaseType.FREIGHTER:
                base_data.base_name = freighter_name
                freighter = base_data
            case BaseType.EXTERNAL_BASE:
                if base_data.parts_count > 0:
                    external.append(base_data)
        
        if base_data.base_type != BaseType.EXTERNAL_BASE:
            total_parts_count += base_data.parts_count
    
    # sort list of corvettes with restpect to their user_data, because user_data represents location of corvette in ship slots making it wasy to read
    corvettes.sort(key=lambda x: x.user_data)
    
    # return combined list in an object for easy search
    return {
        "corvettes":corvettes,
        "bases":bases,
        "external":external,
        "freighter": freighter,
        "total_parts_count": total_parts_count,
    }
    
    
# active save is most recently modified save file
def get_lastes_save_file_location(save_slot):
    save_1 = Path(save_slot[0])
    save_2 = Path(save_slot[1])
    
    # store when last time these save files were modified
    m_time_save_1 = os.path.getmtime(save_1)
    m_time_save_2 = os.path.getmtime(save_2)
    
    # return the save file that was most recently modified
    return save_1 if m_time_save_1 > m_time_save_2 else save_2
    
# helper function that gives returns save file object for easier loading
def get_save_file(save_slot):
    save_location = get_lastes_save_file_location(save_slot)
    from .save_file import SaveFile
    save_file = SaveFile(save_location)
    return save_file


# impart a base from save file
def import_paticular_base_from_save(base_identifier,  save_slot):
    save_file = get_save_file(save_slot)
    data = save_file.load()
    
    # fist see if base actially exists or not
    searched_base = save_file.search_base_with_identifier(base_identifier)
    if searched_base is None:
        message = (
            "Error importing base"
        )
        ShowMessageBox(message = message, title="Export Failed", icon = "WARNING_LARGE")
        return None
        
    #return bases after translating it to engish
    return save_translation.translate_to_eng_data(searched_base)
    
#save a base to save file
def save_base_to_save_file(objects_data, base_identifier,  save_slot, new_base_name = None):
    save_file = get_save_file(save_slot)
    data = save_file.load()
    
    # look for base in save file to see it it exist or not
    in_base = save_file.search_base_with_identifier(base_identifier)
    if in_base is None:
        return
    
    # here update objects list with list provided
    in_base[SaveTranslation.objects] = save_translation.translate_to_obf_data(objects_data)
    
    
    # update name of base if provided
    if new_base_name is not None:
        ship_ownsership = save_file.get_ship_ownership_pointer()
        in_base[SaveTranslation.base_name] = new_base_name
        ship_ownsership[base_identifier["user_data"]][SaveTranslation.base_name] = new_base_name
    
    # save the file and make backup after update it
    save_file.make_backup()
    save_file.save()
    return "Base/Corvette saved sucessfully"

# a folder within save_directory , where backups will be stored
def get_backups_folder(save_links):
    folder = os.path.dirname(save_links[0])
    backup_folder = os.path.join(
        folder,"nms_base_builder_backup",
    )
    #create backup folder if it doesnt exist
    os.makedirs(backup_folder, exist_ok=True)
    return backup_folder

# make backup of both save files linked to a save slot
def backup_save_files(save_links):
    backup_folder = get_backups_folder(save_links)
    # add date and time in bakcup file's name to make make manual searching easier
    dat_and_time = datetime.now().strftime("d-%Y-%m-%d_t-%H-%M-%S-%f")[:-3]
    
    
    save_1 = save_links[0]
    save_2 = save_links[1]
    s1_name, s1_ext = os.path.splitext(os.path.basename(save_1))
    s2_name, s2_ext = os.path.splitext(os.path.basename(save_2))
    
    save_1_backup = os.path.join(
        backup_folder,
        f"{s1_name}{s1_ext}.{dat_and_time}.blender.bak"
    )
    save_2_backup = os.path.join(
        backup_folder,
        f"{s2_name}{s2_ext}.{dat_and_time}.blender.bak"
    )
    
    #make exact copies of those sace files and just change names
    shutil.copy2(save_1, save_1_backup)
    shutil.copy2(save_2, save_2_backup)
    
# Open backup for each OS type
def open_backup_folder_in_explorer(save_links):
    backup_folder = Path(get_backups_folder(save_links))

    if not backup_folder.exists():
        print(f"Folder does not exist: {backup_folder}")
        return

    if sys.platform == "win32": #windows
        os.startfile(backup_folder)
    elif sys.platform == "darwin":  # macOS
        subprocess.Popen(["open", str(backup_folder)])
    else:  # Linux
        subprocess.Popen(["xdg-open", str(backup_folder)])
    
# Check if a path string is valid NMS save folder or not
def validate_save_folder(save_folder):
    save_folder = Path(save_folder)
    return save_folder.parts[-2:] == ("HelloGames", "NMS")
    
    
