using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json;

namespace UnshackleGUI
{
    public partial class MainForm : Form
    {
        private string _configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json");
        private string _paramsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "params.json");

        private AppSettings _appSettings = new AppSettings();
        private List<UnshackleParameter> _parameterDefinitions = new List<UnshackleParameter>();
        private Dictionary<string, object> _propertyValues = new Dictionary<string, object>();

        private Process? _currentProcess;

        public MainForm()
        {
            InitializeComponent();
            ApplyDarkTheme();
            LoadParamsAndConfig();

            // EVENT: This updates the command line the moment a value changes
            pgProfile.PropertyValueChanged += (s, e) =>
            {
                UpdateCommandPreview();

                if (comboProfiles.SelectedItem != null)
                {
                    string selected = comboProfiles.SelectedItem.ToString();
                    _appSettings.Profiles[selected] =
                        new Dictionary<string, object>(_propertyValues);
                }

                SaveConfig();
            };

            comboService.SelectedIndexChanged += (s, e) => UpdateCommandPreview();
            txtURL.TextChanged += (s, e) => UpdateCommandPreview();
            txtBinName.TextChanged += (s, e) => UpdateCommandPreview();

            btnRun.Click += btnRun_Click;
            btnStop.Click += btnStop_Click;
            btnBrowse.Click += btnBrowse_Click;
            btnOpenCookies.Click += btnOpenCookies_Click;
            btnEditServiceConfig.Click += btnEditServiceConfig_Click;
            btnClearLog.Click += btnClearLog_Click;
            btnEditYaml.Click += btnEditYaml_Click;
            btnAddProfile.Click += btnAddProfile_Click;
            btnRemoveProfile.Click += btnRemoveProfile_Click;
        }

        private void LoadParamsAndConfig()
        {
            // 1. Load Parameters (Rules)
            if (File.Exists(_paramsPath))
            {
                var json = File.ReadAllText(_paramsPath);
                _parameterDefinitions = JsonConvert.DeserializeObject<List<UnshackleParameter>>(json) ?? new List<UnshackleParameter>();

                foreach (var p in _parameterDefinitions)
                {
                    // Ensure every parameter has a value in the dictionary
                    if (!_propertyValues.ContainsKey(p.Name))
                        _propertyValues[p.Name] = p.Default ?? "";
                }
            }

            // 2. Load Config (Paths/Profiles)
            if (File.Exists(_configPath))
            {
                var json = File.ReadAllText(_configPath);
                _appSettings = JsonConvert.DeserializeObject<AppSettings>(json) ?? new AppSettings();
                txtRootPath.Text = _appSettings.RootPath;
                txtBinName.Text = _appSettings.BinaryName;
            }

            if (_appSettings.Profiles == null || _appSettings.Profiles.Count == 0)
            {
                _appSettings.Profiles = new Dictionary<string, Dictionary<string, object>>();
                _appSettings.Profiles["Default"] = new Dictionary<string, object>(_propertyValues);
            }

            comboProfiles.DataSource = null;
            comboProfiles.DataSource = _appSettings.Profiles.Keys.ToList();

            comboProfiles.SelectedIndexChanged += (s, e) =>
            {
                if (comboProfiles.SelectedItem == null) return;

                string selected = comboProfiles.SelectedItem.ToString();

                if (_appSettings.Profiles.TryGetValue(selected, out var values))
                {
                    _propertyValues = new Dictionary<string, object>(values);

                    pgProfile.SelectedObject =
                        new DynamicObject(_propertyValues, _parameterDefinitions);

                    UpdateCommandPreview();
                }
            };


            // 3. Bind to Grid using the FIXED Descriptor
            pgProfile.SelectedObject = new DynamicObject(_propertyValues, _parameterDefinitions);


            RefreshFolders();
            UpdateCommandPreview();
        }

        private void UpdateCommandPreview()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append($"{txtBinName.Text} run unshackle dl ");

            foreach (var p in _parameterDefinitions)
            {
                if (!_propertyValues.TryGetValue(p.Name, out object val)) continue;

                // Handle Booleans
                if (p.Type == "Bool" && val is bool b && b)
                {
                    sb.Append($"{p.Flag} ");
                }
                // Handle Text/Selection/Numbers
                else if (p.Type != "Bool" && val != null)
                {
                    string sVal = val.ToString();
                    // Skip empty, "any", or "0" (for default bitrates)
                    if (!string.IsNullOrWhiteSpace(sVal) && sVal != "any" && sVal != "0")
                    {
                        if (sVal.Contains(" ")) sVal = $"\"{sVal}\"";
                        sb.Append($"{p.Flag} {sVal} ");
                    }
                }
            }

            sb.Append($"{comboService.Text} {txtURL.Text}");
            txtCommandPreview.Text = sb.ToString();
        }

        private void btnEditServiceConfig_Click(object? sender, EventArgs e)
        {
            string yamlPath = Path.Combine(txtRootPath.Text, "unshackle", "services", comboService.Text, "config.yaml");
            if (File.Exists(yamlPath))
                Process.Start(new ProcessStartInfo(yamlPath) { UseShellExecute = true });
            else
                MessageBox.Show($"No config.yaml found for {comboService.Text}");
        }

        private async void btnRun_Click(object? sender, EventArgs e)
        {
            string finalCmd = txtCommandPreview.Text;
            if (string.IsNullOrWhiteSpace(finalCmd)) return;

            ToggleUI(true);
            txtLog.AppendText($"> Executing: {finalCmd}{Environment.NewLine}");

            try { await Task.Run(() => RunCli(finalCmd)); }
            catch (Exception ex) { AppendLog($"[ERROR]: {ex.Message}"); }
            finally { ToggleUI(false); }
        }

        private void RunCli(string cmd)
        {
            ProcessStartInfo psi = new ProcessStartInfo
            {
                FileName = "cmd.exe",
                // /c = Run command and then terminate
                // chcp 65001 = Force console to use UTF-8 encoding
                // && = Run the actual command immediately after setting encoding
                Arguments = $"/c chcp 65001 && {cmd}",
                WorkingDirectory = txtRootPath.Text,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
                StandardOutputEncoding = Encoding.UTF8
            };

            // Ensure Python scripts know to use UTF-8
            psi.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8";

            using (_currentProcess = Process.Start(psi))
            {
                if (_currentProcess == null) return;

                _currentProcess.OutputDataReceived += (s, e) =>
                {
                    // Filter out the noisy "Active code page: 65001" message from CMD
                    if (!string.IsNullOrEmpty(e.Data) && !e.Data.Contains("Active code page"))
                        AppendLog(e.Data);
                };

                _currentProcess.ErrorDataReceived += (s, e) => AppendLog(e.Data);

                _currentProcess.BeginOutputReadLine();
                _currentProcess.BeginErrorReadLine();
                _currentProcess.WaitForExit();
            }
        }

        private void AppendLog(string? text)
        {
            if (string.IsNullOrEmpty(text)) return;
            this.BeginInvoke(new Action(() => { txtLog.AppendText(text + Environment.NewLine); }));
        }

        private void ApplyDarkTheme()
        {
            this.BackColor = Color.FromArgb(30, 30, 30);
            this.ForeColor = Color.White;
            foreach (Control c in this.Controls) UpdateControlTheme(c);
        }

        private void UpdateControlTheme(Control c)
        {
            c.BackColor = Color.FromArgb(45, 45, 48);
            c.ForeColor = Color.White;
            if (c is TextBox tb) tb.BorderStyle = BorderStyle.FixedSingle;
            if (c is Button btn) { btn.FlatStyle = FlatStyle.Flat; btn.FlatAppearance.BorderColor = Color.Gray; }
            if (c is PropertyGrid pg)
            {
                pg.BackColor = Color.FromArgb(37, 37, 38);
                pg.ViewBackColor = Color.FromArgb(37, 37, 38);
                pg.ViewForeColor = Color.White;
                pg.LineColor = Color.FromArgb(45, 45, 48);
            }
            foreach (Control child in c.Controls) UpdateControlTheme(child);
        }

        private void SaveConfig()
        {
            _appSettings.RootPath = txtRootPath.Text;
            _appSettings.BinaryName = txtBinName.Text;
            File.WriteAllText(_configPath, JsonConvert.SerializeObject(_appSettings, Formatting.Indented));
        }

        private void RefreshFolders()
        {
            comboService.Items.Clear();
            string path = Path.Combine(txtRootPath.Text, "unshackle", "services");
            if (Directory.Exists(path))
            {
                var dirs = Directory.GetDirectories(path).Select(Path.GetFileName).ToArray();
                comboService.Items.AddRange(dirs.Cast<object>().ToArray());
                if (comboService.Items.Count > 0) comboService.SelectedIndex = 0;
            }
        }


        private void ComboProfiles_SelectedIndexChanged(object? sender, EventArgs e)
        {
            if (comboProfiles.SelectedItem == null) return;

            string selected = comboProfiles.SelectedItem.ToString();

            if (_appSettings.Profiles.TryGetValue(selected, out var values))
            {
                _propertyValues = new Dictionary<string, object>(values);

                pgProfile.SelectedObject =
                    new DynamicObject(_propertyValues, _parameterDefinitions);

                UpdateCommandPreview();
            }
        }


        private void btnAddProfile_Click(object? sender, EventArgs e)
        {
            string name = Microsoft.VisualBasic.Interaction.InputBox(
                "Enter profile name:",
                "New Profile",
                "NewProfile");

            if (string.IsNullOrWhiteSpace(name))
                return;

            if (_appSettings.Profiles.ContainsKey(name))
            {
                MessageBox.Show("Profile already exists.");
                return;
            }

            // Clone current settings into new profile
            _appSettings.Profiles[name] =
                new Dictionary<string, object>(_propertyValues);

            comboProfiles.DataSource = null;
            comboProfiles.DataSource = _appSettings.Profiles.Keys.ToList();
            comboProfiles.SelectedItem = name;

            SaveConfig();
        }

        private void btnRemoveProfile_Click(object? sender, EventArgs e)
        {
            if (comboProfiles.SelectedItem == null)
                return;

            string selected = comboProfiles.SelectedItem.ToString();

            if (selected == "Default")
            {
                MessageBox.Show("Default profile cannot be deleted.");
                return;
            }

            if (!_appSettings.Profiles.ContainsKey(selected))
                return;

            _appSettings.Profiles.Remove(selected);

            comboProfiles.DataSource = null;
            comboProfiles.DataSource = _appSettings.Profiles.Keys.ToList();

            comboProfiles.SelectedIndex = 0;

            SaveConfig();
        }



        private void ToggleUI(bool r) => this.BeginInvoke(new Action(() => { btnRun.Enabled = !r; btnStop.Visible = r; }));
        private void btnStop_Click(object? sender, EventArgs e) { _currentProcess?.Kill(true); }
        private void btnClearLog_Click(object? sender, EventArgs e) => txtLog.Clear();
        private void btnBrowse_Click(object? sender, EventArgs e)
        {
            using (var fbd = new FolderBrowserDialog()) if (fbd.ShowDialog() == DialogResult.OK) { txtRootPath.Text = fbd.SelectedPath; SaveConfig(); RefreshFolders(); }
        }
        private void btnOpenCookies_Click(object? sender, EventArgs e)
        {
            string p = Path.Combine(txtRootPath.Text, "unshackle", "cookies", comboService.Text);
            Directory.CreateDirectory(p); Process.Start("explorer.exe", p);
        }
        private void btnEditYaml_Click(object? sender, EventArgs e)
        {
            string yamlPath = Path.Combine(txtRootPath.Text, "unshackle/unshackle.yaml");
            if (File.Exists(yamlPath)) Process.Start(new ProcessStartInfo(yamlPath) { UseShellExecute = true });
        }
    }



    // --- DATA MODELS ---
    public class AppSettings
    {
        public string RootPath { get; set; } = @"C:\DEVINE\unshackle";
        public string BinaryName { get; set; } = "uv";

        public Dictionary<string, Dictionary<string, object>> Profiles { get; set; }
            = new Dictionary<string, Dictionary<string, object>>();
    }

    public class UnshackleParameter
    {
        public string Flag { get; set; } = "";
        public string Name { get; set; } = "";
        public string Type { get; set; } = "Text";
        public List<string>? Options { get; set; }
        public object? Default { get; set; }
        public string Category { get; set; } = "Misc";
    }
}