using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
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 SchedulerForm : Form
    {
        // ==========================================
        //  VARIABLES & PATHS
        // ==========================================
        private string _jobsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "jobs.json");
        private string _successStringsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "success_strings.txt");

        private BindingList<SchedulerJob> _jobs = new BindingList<SchedulerJob>();
        private List<string> _successKeywords = new List<string>();
        private System.Windows.Forms.Timer _runnerTimer;

        private List<string> _profiles;
        private string _rootPath;
        private string _binaryName;
        private SchedulerJob? _editingJob = null;

        // Flag to allow the Main Form to kill this window completely
        private bool _forceClose = false;

        // ==========================================
        //  CONSTRUCTOR
        // ==========================================
        public SchedulerForm(List<string> profiles, string rootPath, string binaryName)
        {
            InitializeComponent();
            _profiles = profiles;
            _rootPath = rootPath;
            _binaryName = binaryName;

            LoadSuccessStrings();
            InitializeCustomUI();
            LoadJobs();

            // Setup Background Runner Timer
            _runnerTimer = new System.Windows.Forms.Timer();
            _runnerTimer.Interval = 10000; // Runs every 10 seconds
            _runnerTimer.Tick += CheckAndRunJobs;
            _runnerTimer.Start();
        }

        // ==========================================
        //  PUBLIC CONTROL METHODS
        // ==========================================

        // Called by MainForm when "Stop" is clicked
        public void Shutdown()
        {
            _forceClose = true;
            _runnerTimer.Stop();
            this.Close();
        }

        // ==========================================
        //  INITIALIZATION & UI WIRING
        // ==========================================
        private void InitializeCustomUI()
        {
            // Disable "New Row" to prevent ghost jobs
            gridJobs.AllowUserToAddRows = false;

            // 1. Setup Dropdowns & Data
            comboProfile.DataSource = _profiles;
            gridJobs.AutoGenerateColumns = false;
            gridJobs.DataSource = _jobs;

            // 2. Clear and Define Grid Columns
            gridJobs.Columns.Clear();

            gridJobs.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = "Name",
                HeaderText = "Name",
                Width = 150
            });

            gridJobs.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = "Recurrence",
                HeaderText = "Trigger",
                Width = 80
            });

            gridJobs.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = "ScheduledTime",
                HeaderText = "Next Run",
                Width = 130
            });

            gridJobs.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = "Status",
                HeaderText = "Status",
                Width = 120
            });

            // URL (Auto-Fill)
            var urlCol = new DataGridViewTextBoxColumn
            {
                DataPropertyName = "Url",
                HeaderText = "URL"
            };
            urlCol.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            gridJobs.Columns.Add(urlCol);

            // Active
            gridJobs.Columns.Add(new DataGridViewCheckBoxColumn
            {
                DataPropertyName = "Enabled",
                HeaderText = "Active",
                Width = 60
            });

            // 3. Force Row Height
            gridJobs.RowTemplate.Height = 30;

            // 4. Wire Up Events
            btnAddUpdate.Click += BtnAdd_Click;
            btnToolSave.Click += (s, e) => SaveJobs();
            btnToolNew.Click += (s, e) => ClearInputs();
            btnToolDelete.Click += BtnRemove_Click;
            btnToolRun.Click += BtnRunNow_Click;
            menuRun.Click += BtnRunNow_Click;
            menuDelete.Click += BtnRemove_Click;
            gridJobs.SelectionChanged += GridJobs_SelectionChanged;
            rbWeekly.CheckedChanged += (s, e) => pnlWeekly.Visible = rbWeekly.Checked;

            // 5. Auto-Save on Checkbox Click
            gridJobs.CellValueChanged += (s, e) =>
            {
                SaveJobs();
            };

            gridJobs.CurrentCellDirtyStateChanged += (s, e) =>
            {
                if (gridJobs.IsCurrentCellDirty)
                {
                    gridJobs.CommitEdit(DataGridViewDataErrorContexts.Commit);
                }
            };

            // 6. Start Clean
            ClearInputs();
        }

        private void LoadSuccessStrings()
        {
            if (File.Exists(_successStringsPath))
            {
                try
                {
                    _successKeywords = File.ReadAllLines(_successStringsPath)
                                           .Where(line => !string.IsNullOrWhiteSpace(line))
                                           .Select(line => line.Trim().ToLower())
                                           .ToList();
                }
                catch
                {
                    // Ignore errors
                }
            }

            if (_successKeywords.Count == 0)
            {
                _successKeywords = new List<string> { "downloaded in", "processed all titles", "100%" };
                try
                {
                    File.WriteAllLines(_successStringsPath, _successKeywords);
                }
                catch { }
            }
        }

        // ==========================================
        //  UI INTERACTION LOGIC
        // ==========================================
        private void GridJobs_SelectionChanged(object? sender, EventArgs e)
        {
            if (gridJobs.CurrentRow?.DataBoundItem is SchedulerJob job)
            {
                _editingJob = job;

                txtName.Text = job.Name;
                txtUrl.Text = job.Url;

                if (comboProfile.Items.Contains(job.ProfileName))
                    comboProfile.SelectedItem = job.ProfileName;

                dtPicker.Value = job.ScheduledTime;
                numRetries.Value = job.MaxRetries;
                txtPostCmd.Text = job.PostRunCommand;
                numRetryDelay.Value = job.RetryDelayInMinutes;

                rbOneTime.Checked = (job.Recurrence == "OneTime");
                rbDaily.Checked = (job.Recurrence == "Daily");
                rbWeekly.Checked = (job.Recurrence == "Weekly");

                chkMon.Checked = job.WeeklyDays.Contains("Monday");
                chkTue.Checked = job.WeeklyDays.Contains("Tuesday");
                chkWed.Checked = job.WeeklyDays.Contains("Wednesday");
                chkThu.Checked = job.WeeklyDays.Contains("Thursday");
                chkFri.Checked = job.WeeklyDays.Contains("Friday");
                chkSat.Checked = job.WeeklyDays.Contains("Saturday");
                chkSun.Checked = job.WeeklyDays.Contains("Sunday");

                if (lblRetryStatus != null)
                    lblRetryStatus.Text = $"Attempt: {job.CurrentRetries}/{job.MaxRetries}";

                btnAddUpdate.Text = "Update Task";
                btnAddUpdate.BackColor = System.Drawing.Color.DarkOrange;
                btnAddUpdate.ForeColor = System.Drawing.Color.Black;
            }
        }

        private void ClearInputs()
        {
            _editingJob = null;

            txtName.Clear();
            txtUrl.Clear();
            txtPostCmd.Clear();
            numRetries.Value = 3;
            numRetryDelay.Value = 15;
            dtPicker.Value = DateTime.Now;

            if (lblRetryStatus != null)
                lblRetryStatus.Text = "Attempt: 0";

            rbOneTime.Checked = true;
            foreach (Control c in pnlWeekly.Controls)
            {
                if (c is CheckBox cb) cb.Checked = false;
            }

            btnAddUpdate.Text = "Add Task";
            btnAddUpdate.BackColor = System.Drawing.Color.FromArgb(60, 60, 60);
            btnAddUpdate.ForeColor = System.Drawing.Color.White;

            gridJobs.ClearSelection();
        }

        private void BtnAdd_Click(object? sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtUrl.Text))
            {
                MessageBox.Show("Please enter a URL or ID.", "Missing Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            SchedulerJob job;

            if (_editingJob != null)
            {
                job = _editingJob;
            }
            else
            {
                job = new SchedulerJob();
            }

            job.Name = string.IsNullOrWhiteSpace(txtName.Text) ? "New Task" : txtName.Text;
            job.Url = txtUrl.Text;
            job.ProfileName = comboProfile.Text;
            job.ScheduledTime = dtPicker.Value;
            job.MaxRetries = (int)numRetries.Value;
            job.RetryDelayInMinutes = (int)numRetryDelay.Value;
            job.PostRunCommand = txtPostCmd.Text;

            if (rbDaily.Checked) job.Recurrence = "Daily";
            else if (rbWeekly.Checked) job.Recurrence = "Weekly";
            else job.Recurrence = "OneTime";

            job.WeeklyDays.Clear();
            if (chkMon.Checked) job.WeeklyDays.Add("Monday");
            if (chkTue.Checked) job.WeeklyDays.Add("Tuesday");
            if (chkWed.Checked) job.WeeklyDays.Add("Wednesday");
            if (chkThu.Checked) job.WeeklyDays.Add("Thursday");
            if (chkFri.Checked) job.WeeklyDays.Add("Friday");
            if (chkSat.Checked) job.WeeklyDays.Add("Saturday");
            if (chkSun.Checked) job.WeeklyDays.Add("Sunday");

            // Enable and Reset
            job.Enabled = true;
            job.Status = "Pending";
            job.CurrentRetries = 0;

            // Recalculate Date Immediately
            if (job.Recurrence != "OneTime")
            {
                CalculateNextRun(job);
            }

            if (_editingJob == null)
            {
                _jobs.Add(job);
            }
            else
            {
                MessageBox.Show("Task Updated!");
            }

            SaveJobs();
            gridJobs.Refresh();
            ClearInputs();
        }

        private void BtnRemove_Click(object? sender, EventArgs e)
        {
            if (gridJobs.CurrentRow?.DataBoundItem is SchedulerJob job)
            {
                if (MessageBox.Show($"Delete task '{job.Name}'?", "Confirm Delete", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
                {
                    _jobs.Remove(job);
                    SaveJobs();
                    ClearInputs();
                }
            }
        }

        private async void BtnRunNow_Click(object? sender, EventArgs e)
        {
            if (gridJobs.CurrentRow?.DataBoundItem is SchedulerJob job)
            {
                if (MessageBox.Show($"Run '{job.Name}' immediately?", "Run Now", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    AppendLog($"[USER] Manually triggered run for '{job.Name}'...");
                    await RunJob(job);
                    MessageBox.Show("Run completed. Check log for details.");
                }
            }
            else
            {
                MessageBox.Show("Please select a task to run.");
            }
        }

        // ==========================================
        //  BACKGROUND RUNNER ENGINE
        // ==========================================
        private async void CheckAndRunJobs(object? sender, EventArgs e)
        {
            var now = DateTime.Now;

            var dueJobs = _jobs.Where(j =>
                j.Enabled &&
                (j.Status == "Pending" || j.Status.StartsWith("Retrying")) &&
                j.ScheduledTime <= now).ToList();

            foreach (var job in dueJobs)
            {
                await RunJob(job);
            }
        }

        private async Task RunJob(SchedulerJob job)
        {
            job.Status = "Running...";
            gridJobs.Refresh();

            string cmd = BuildCommand(job);
            AppendLog($"Executing Job '{job.Name}'...");
            AppendLog($"[CMD]: {cmd}");

            var result = await RunProcessAsync(cmd, _rootPath);
            int exitCode = result.ExitCode;
            string output = result.Output.ToLower();

            bool success = _successKeywords.Any(k => output.Contains(k)) || exitCode == 0;

            if (output.Contains("already exists") || output.Contains("skipping"))
            {
                AppendLog($"[INFO] Job '{job.Name}' skipped (File exists).");
                HandleSuccess(job);
            }
            else if (success)
            {
                AppendLog($"[SUCCESS] Job '{job.Name}' finished.");

                if (!string.IsNullOrWhiteSpace(job.PostRunCommand))
                {
                    AppendLog($"Running Post-Command...");
                    RunPostCommand(job);
                }

                HandleSuccess(job);
            }
            else if (output.Contains("unauthorized") || output.Contains("login failed") || output.Contains("403"))
            {
                job.Status = "Auth Failed";
                job.Enabled = false;
                AppendLog($"[FATAL] Job '{job.Name}' auth failed.");
            }
            else
            {
                HandleFailure(job);
            }

            SaveJobs();
            gridJobs.Refresh();

            if (_editingJob == job && lblRetryStatus != null)
            {
                lblRetryStatus.Text = $"Attempt: {job.CurrentRetries}/{job.MaxRetries}";
            }
        }

        private void HandleSuccess(SchedulerJob job)
        {
            job.CurrentRetries = 0;

            if (job.Recurrence == "OneTime")
            {
                job.Status = "Success";
                job.Enabled = false;
            }
            else
            {
                CalculateNextRun(job);
                job.Status = "Pending";
                job.Enabled = true;
                AppendLog($"-> Rescheduled '{job.Name}' to {job.ScheduledTime}");
            }
        }

        private void HandleFailure(SchedulerJob job)
        {
            if (job.CurrentRetries < job.MaxRetries)
            {
                job.CurrentRetries++;
                job.Status = $"Retrying ({job.CurrentRetries}/{job.MaxRetries})";

                int delay = job.RetryDelayInMinutes > 0 ? job.RetryDelayInMinutes : 15;
                job.ScheduledTime = DateTime.Now.AddMinutes(delay);

                AppendLog($"[WARNING] Job '{job.Name}' failed. Retrying in {delay}m.");
            }
            else
            {
                job.Status = "Failed";
                job.Enabled = false;
                AppendLog($"[FAILURE] Job '{job.Name}' failed permanently.");
            }
        }

        private void CalculateNextRun(SchedulerJob job)
        {
            DateTime checkDate = job.ScheduledTime;

            if (job.Recurrence == "Daily")
            {
                while (checkDate <= DateTime.Now)
                {
                    checkDate = checkDate.AddDays(1);
                }
            }
            else if (job.Recurrence == "Weekly" && job.WeeklyDays.Count > 0)
            {
                while (true)
                {
                    bool isFuture = checkDate > DateTime.Now;
                    bool isCorrectDay = job.WeeklyDays.Contains(checkDate.DayOfWeek.ToString());

                    if (isFuture && isCorrectDay)
                    {
                        break;
                    }

                    checkDate = checkDate.AddDays(1);
                }
            }

            job.ScheduledTime = checkDate;
        }

        // ==========================================
        //  HELPER METHODS
        // ==========================================

        private void LoadJobs()
        {
            if (File.Exists(_jobsPath))
            {
                try
                {
                    string json = File.ReadAllText(_jobsPath);
                    var list = JsonConvert.DeserializeObject<List<SchedulerJob>>(json);

                    if (list != null)
                    {
                        _jobs = new BindingList<SchedulerJob>(list);
                    }
                }
                catch
                {
                    // Handle corrupt JSON
                }
            }
            gridJobs.DataSource = _jobs;
        }

        private void SaveJobs()
        {
            try
            {
                string json = JsonConvert.SerializeObject(_jobs, Formatting.Indented);
                File.WriteAllText(_jobsPath, json);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error saving jobs: {ex.Message}");
            }
        }

        private string BuildCommand(SchedulerJob job)
        {
            string profilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles", $"{job.ProfileName}.json");
            var props = new Dictionary<string, object>();

            if (File.Exists(profilePath))
            {
                props = JsonConvert.DeserializeObject<Dictionary<string, object>>(File.ReadAllText(profilePath))
                        ?? new Dictionary<string, object>();
            }

            string paramsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "params.json");
            var defs = JsonConvert.DeserializeObject<List<UnshackleParameter>>(File.ReadAllText(paramsPath))
                       ?? new List<UnshackleParameter>();

            StringBuilder sb = new StringBuilder();
            sb.Append($"{_binaryName} run unshackle dl ");

            foreach (var p in defs)
            {
                if (props.TryGetValue(p.Name, out object val))
                {
                    if (p.Type == "Bool" && val is bool b && b)
                    {
                        sb.Append($"{p.Flag} ");
                    }
                    else if (p.Type != "Bool" && val != null)
                    {
                        string sVal = val.ToString();
                        if (!string.IsNullOrWhiteSpace(sVal) && sVal != "any" && sVal != "0")
                        {
                            if (sVal.Contains(" "))
                            {
                                sVal = $"\"{sVal}\"";
                            }
                            sb.Append($"{p.Flag} {sVal} ");
                        }
                    }
                }
            }

            if (props.TryGetValue("Service", out object serviceObj))
            {
                sb.Append($"{serviceObj} ");
            }

            string cleanUrl = job.Url.Trim();
            cleanUrl = cleanUrl.Trim('"');
            cleanUrl = cleanUrl.Trim('\'');

            sb.Append($"{cleanUrl}");

            return sb.ToString();
        }

        private void RunPostCommand(SchedulerJob job)
        {
            try
            {
                ProcessStartInfo psi = new ProcessStartInfo();
                psi.FileName = "cmd.exe";
                psi.Arguments = $"/c {job.PostRunCommand}";
                psi.UseShellExecute = true;
                psi.CreateNoWindow = true;

                Process.Start(psi);
            }
            catch (Exception ex)
            {
                AppendLog($"[ERR] Post-Command failed: {ex.Message}");
            }
        }

        private void AppendLog(string msg)
        {
            if (this.IsDisposed) return;

            string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            string line = $"[{timestamp}] {msg}";

            this.BeginInvoke(new Action(() =>
            {
                if (txtLog != null && !txtLog.IsDisposed)
                {
                    txtLog.AppendText(line + Environment.NewLine);
                }
            }));

            try
            {
                File.AppendAllText("scheduler.log", line + Environment.NewLine);
            }
            catch
            {
                // Ignore file lock issues
            }
        }

        private Task<(int ExitCode, string Output)> RunProcessAsync(string cmd, string workDir)
        {
            var tcs = new TaskCompletionSource<(int, string)>();
            StringBuilder outputBuilder = new StringBuilder();

            ProcessStartInfo psi = new ProcessStartInfo();
            psi.FileName = "cmd.exe";
            psi.Arguments = $"/c chcp 65001 && {cmd}";
            psi.WorkingDirectory = workDir;
            psi.UseShellExecute = false;
            psi.CreateNoWindow = true;
            psi.RedirectStandardOutput = true;
            psi.RedirectStandardError = true;
            psi.StandardOutputEncoding = Encoding.UTF8;
            psi.StandardErrorEncoding = Encoding.UTF8;

            psi.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8";
            psi.EnvironmentVariables["PYTHONUTF8"] = "1";

            var p = new Process { StartInfo = psi, EnableRaisingEvents = true };

            p.OutputDataReceived += (s, e) =>
            {
                if (!string.IsNullOrWhiteSpace(e.Data))
                {
                    AppendLog($"[CMD]: {e.Data}");
                    outputBuilder.AppendLine(e.Data);
                }
            };

            p.ErrorDataReceived += (s, e) =>
            {
                if (!string.IsNullOrWhiteSpace(e.Data))
                {
                    AppendLog($"[ERR]: {e.Data}");
                    outputBuilder.AppendLine(e.Data);
                }
            };

            p.Exited += (s, e) =>
            {
                tcs.SetResult((p.ExitCode, outputBuilder.ToString()));
                p.Dispose();
            };

            try
            {
                p.Start();
                p.BeginOutputReadLine();
                p.BeginErrorReadLine();
            }
            catch (Exception ex)
            {
                AppendLog($"[FATAL] Start Error: {ex.Message}");
                tcs.SetResult((-1, ""));
            }

            return tcs.Task;
        }

        // ==========================================
        //  PREVENT CLOSING (HIDE WINDOW)
        // ==========================================
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            // If the user clicks X, we hide it.
            // If Shutdown() is called, _forceClose is true, so we close it.
            if (e.CloseReason == CloseReason.UserClosing && !_forceClose)
            {
                e.Cancel = true;
                this.Hide();
            }
            else
            {
                base.OnFormClosing(e);
            }
        }
    }
}