using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// GAME 1303 Tier 7 — Writes the game state to a JSON file and reads it back.
/// Cozy Critters (essential — offline decay), Zap Attack, MythoClash, Anabasis,
/// Store Simulator.
///
/// WHERE THE FILE GOES: Application.persistentDataPath, which is a real folder on disk
/// (Windows: %AppData%/../LocalLow/<Company>/<Product>). The Log Save Path option prints
/// it to the Console so students can open it in Notepad and SEE their save — that moment
/// is worth more than any explanation of serialization.
///
/// SETUP
///  1. One empty GameObject "SaveSystem" with this script. Tick Persist Across Scenes.
///  2. Tick which systems to include. Each one is optional and safely skipped if the
///     matching component isn't in the scene.
///  3. Saving:
///       - Auto Save Every N seconds (0 = off)
///       - Save On Quit (recommended — catches the alt-F4 case)
///       - or call SaveSystem.Instance.Save() from a button / a checkpoint.
///  4. Loading: Load On Start reads the file when the game opens. Apply Offline Decay
///     tells every NeedStat how long the player has been away, which is exactly Rhys's
///     Tamagotchi model without the punishment.
///  5. Slots: Slot Name lets you have "save1", "save2", or one per player.
///
/// This deliberately replaces nothing: Tier 4's LevelProgress (PlayerPrefs) still handles
/// level unlocks and best times. Use this for the *state of the world*, that for *progress*.
/// </summary>
public class SaveSystem : MonoBehaviour
{
    public static SaveSystem Instance { get; private set; }

    [Header("File")]
    public string slotName = "save1";
    public bool prettyPrint = true;
    public bool logSavePath = true;

    [Header("What to include")]
    public bool saveFlags = true;
    public bool saveCollectibles = true;
    public bool savePlayerPosition = false;
    public bool savePetStats = true;
    public bool saveClues = true;

    [Header("When")]
    public bool loadOnStart = true;
    public bool saveOnQuit = true;
    [Tooltip("Seconds between automatic saves. 0 = off.")]
    public float autoSaveEvery = 60f;
    public bool persistAcrossScenes = true;

    [Header("Offline")]
    [Tooltip("Cozy Critters: decay needs by the real time the player was away.")]
    public bool applyOfflineDecay = true;
    [Tooltip("Cap the offline decay so a week away doesn't empty everything.")]
    public float maxOfflineMinutes = 480f;

    [Header("Events")]
    public UnityEvent OnSaved;
    public UnityEvent OnLoaded;
    public UnityEvent<float> OnReturnedAfterMinutes;   // for a "welcome back" message
    public UnityEvent OnNoSaveFound;

    public string FilePath => Path.Combine(Application.persistentDataPath, slotName + ".json");
    public bool HasSave => File.Exists(FilePath);

    private float autoTimer;

    void Awake()
    {
        if (Instance != null && Instance != this) { Destroy(gameObject); return; }
        Instance = this;
        if (persistAcrossScenes) DontDestroyOnLoad(gameObject);
        if (logSavePath) Debug.Log("SaveSystem: save file is at " + FilePath);
    }

    void Start()
    {
        if (loadOnStart) Load();
        autoTimer = autoSaveEvery;
    }

    void Update()
    {
        if (autoSaveEvery <= 0f) return;
        autoTimer -= Time.unscaledDeltaTime;
        if (autoTimer <= 0f) { autoTimer = autoSaveEvery; Save(); }
    }

    void OnApplicationQuit()   { if (saveOnQuit) Save(); }
    void OnApplicationPause(bool paused) { if (paused && saveOnQuit) Save(); }   // mobile

    // ---------------------------------------------------------------- SAVE
    public void Save()
    {
        var data = new SaveData
        {
            savedAtUtc = DateTime.UtcNow.ToString("o"),
            sceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name
        };

        if (saveFlags && ChoiceFlagManager.Instance != null)
            data.flags = ChoiceFlagManager.Instance.GetAllFlags();

        if (saveCollectibles && CollectionTracker.Instance != null)
        {
            data.score = CollectionTracker.Instance.Score;
            foreach (var kv in CollectionTracker.Instance.GetAllCounts())
            {
                data.itemNames.Add(kv.Key.name);
                data.itemCounts.Add(kv.Value);
            }
        }

        if (savePlayerPosition)
        {
            var p = GameObject.FindGameObjectWithTag("Player");
            if (p != null)
            {
                data.playerX = p.transform.position.x;
                data.playerY = p.transform.position.y;
                data.hasPlayerPosition = true;
                var hp = p.GetComponent<Health>();
                if (hp != null) data.playerHealth = hp.CurrentHealth;
            }
        }

        if (savePetStats)
        {
            foreach (var stat in FindObjectsByType<NeedStat>(FindObjectsSortMode.None))
            {
                data.statNames.Add(stat.statName);
                data.statValues.Add(stat.value);
            }
            var personality = FindFirstObjectByType<PersonalityTracker>();
            if (personality != null && personality.Current != null)
                data.personalityId = personality.Current.id;
        }

        if (saveClues && ClueJournal.Instance != null)
            foreach (var clue in ClueJournal.Instance.allClues)
                if (ClueJournal.Instance.HasFound(clue.id)) data.clueIds.Add(clue.id);

        try
        {
            File.WriteAllText(FilePath, JsonUtility.ToJson(data, prettyPrint));
            OnSaved?.Invoke();
        }
        catch (Exception e)
        {
            Debug.LogError("SaveSystem: could not write the save file. " + e.Message);
        }
    }

    // ---------------------------------------------------------------- LOAD
    public bool Load()
    {
        if (!HasSave) { OnNoSaveFound?.Invoke(); return false; }

        SaveData data;
        try { data = JsonUtility.FromJson<SaveData>(File.ReadAllText(FilePath)); }
        catch (Exception e)
        {
            Debug.LogError("SaveSystem: save file is unreadable, starting fresh. " + e.Message);
            OnNoSaveFound?.Invoke();
            return false;
        }
        if (data == null) { OnNoSaveFound?.Invoke(); return false; }

        float away = Mathf.Min(data.MinutesSinceSaved, maxOfflineMinutes);

        if (saveFlags && ChoiceFlagManager.Instance != null)
            ChoiceFlagManager.Instance.LoadFlags(data.flags);

        if (saveCollectibles && CollectionTracker.Instance != null)
        {
            var lookup = new Dictionary<string, ItemType>();
            foreach (var t in Resources.FindObjectsOfTypeAll<ItemType>()) lookup[t.name] = t;
            CollectionTracker.Instance.ResetAll();
            for (int i = 0; i < data.itemNames.Count && i < data.itemCounts.Count; i++)
                if (lookup.TryGetValue(data.itemNames[i], out var type))
                    CollectionTracker.Instance.Add(type, data.itemCounts[i]);
        }

        if (savePlayerPosition && data.hasPlayerPosition)
        {
            var p = GameObject.FindGameObjectWithTag("Player");
            if (p != null)
            {
                p.transform.position = new Vector3(data.playerX, data.playerY, p.transform.position.z);
                var hp = p.GetComponent<Health>();
                if (hp != null && data.playerHealth > 0) hp.SetCurrentHealth(data.playerHealth);
            }
        }

        if (savePetStats)
        {
            var stats = FindObjectsByType<NeedStat>(FindObjectsSortMode.None);
            for (int i = 0; i < data.statNames.Count && i < data.statValues.Count; i++)
            {
                foreach (var s in stats)
                {
                    if (s.statName != data.statNames[i]) continue;
                    s.SetValue(data.statValues[i]);
                    if (applyOfflineDecay && away > 0f) s.ApplyOfflineTime(away);
                    break;
                }
            }
        }

        if (saveClues && ClueJournal.Instance != null)
            foreach (var id in data.clueIds) ClueJournal.Instance.Discover(id);

        if (away > 1f) OnReturnedAfterMinutes?.Invoke(away);
        OnLoaded?.Invoke();
        return true;
    }

    // ---------------------------------------------------------------- HOUSEKEEPING
    public void DeleteSave()
    {
        if (HasSave) File.Delete(FilePath);
    }

    /// <summary>Wire to a "New Game" button: wipes the file and the in-memory state.</summary>
    public void NewGame()
    {
        DeleteSave();
        ChoiceFlagManager.Instance?.ResetAll();
        CollectionTracker.Instance?.ResetAll();
        ClueJournal.Instance?.ResetJournal();
        DialogueRunner.ResetPlayedHistory();
    }
}