using System.Text;
using Journal.Core.Dtos;
using Journal.Core.Services.Config;
using Journal.Core.Services.Entries;
using Journal.Core.Services.Vault;

namespace Journal.Core.Services.Sidecar;

public sealed class SidecarCli(IVaultStorageService vaultStorage, IEntrySearchService entrySearch, IJournalConfigService config, IEntryFileService entryFiles)
{
    private readonly IVaultStorageService _vaultStorage = vaultStorage;
    private readonly IEntrySearchService _entrySearch = entrySearch;
    private readonly IJournalConfigService _config = config;
    private readonly IEntryFileService _entryFiles = entryFiles;

    public async Task<int> RunAsync(string[] args, Entry entry)
    {
        ArgumentNullException.ThrowIfNull(args);
        ArgumentNullException.ThrowIfNull(entry);

        if (args.Length == 0)
        {
            await entry.RunAsync();
            return 0;
        }

        if (IsHelp(args[0]))
        {
            PrintUsage();
            return 0;
        }

        if (string.Equals(args[0], "vault", StringComparison.OrdinalIgnoreCase))
            return RunVaultCommand([.. args.Skip(1)]);
        if (string.Equals(args[0], "search", StringComparison.OrdinalIgnoreCase))
            return RunSearchCommand([.. args.Skip(1)]);

        Console.Error.WriteLine($"Unknown command: {args[0]}");
        PrintUsage();
        return 2;
    }

    public int RunVaultCommand(string[] args)
    {
        ArgumentNullException.ThrowIfNull(args);
        if (args.Length == 0 || IsHelp(args[0]))
        {
            PrintVaultUsage();
            return 2;
        }

        var action = args[0].Trim().ToLowerInvariant();
        if (action is not ("load" or "save"))
        {
            Console.Error.WriteLine($"Unknown vault action: {args[0]}");
            PrintVaultUsage();
            return 2;
        }

        if (!TryParseVaultOptions([.. args.Skip(1)], out var options, out var parseError))
        {
            Console.Error.WriteLine(parseError);
            PrintVaultUsage();
            return 2;
        }

        var password = options.Password;
        if (string.IsNullOrWhiteSpace(password))
            password = PromptPassword();

        if (string.IsNullOrWhiteSpace(password))
        {
            Console.Error.WriteLine("Vault password cannot be empty.");
            return 2;
        }

        var vaultDirectory = ResolveVaultDirectory(options.VaultDirectory);

        try
        {
            if (action == "load")
            {
                var ok = _vaultStorage.LoadAllVaults(password, vaultDirectory, ResolveVaultStorageDirectory());
                if (!ok)
                {
                    Console.Error.WriteLine("Incorrect password.");
                    return 1;
                }

                Console.WriteLine("Vault loaded.");
                return 0;
            }

            _vaultStorage.RebuildAllVaults(password, vaultDirectory, ResolveVaultStorageDirectory());
            Console.WriteLine("Vault saved.");
            return 0;
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"Vault command failed: {ex.Message}");
            return 1;
        }
    }

    public int RunSearchCommand(string[] args)
    {
        ArgumentNullException.ThrowIfNull(args);
        if (args.Length > 0 && IsHelp(args[0]))
        {
            PrintSearchUsage();
            return 0;
        }

        if (!TryParseSearchOptions(args, out var options, out var parseError))
        {
            Console.Error.WriteLine(parseError);
            PrintSearchUsage();
            return 2;
        }

        var entryCount = _entryFiles.ListEntries().Count;
        if (entryCount == 0)
        {
            Console.WriteLine("No journal entries found. Please load the vault first: journal vault load");
            return 0;
        }

        try
        {
            var request = new EntrySearchRequestDto(
                Query: options.Query,
                Section: options.Section,
                StartDate: options.StartDate,
                EndDate: options.EndDate,
                Tags: options.Tags,
                Types: options.Types,
                Checked: options.Checked,
                Unchecked: options.Unchecked);

            var results = _entrySearch.SearchEntriesAsync(request).GetAwaiter().GetResult();
            if (results.Count == 0)
            {
                Console.WriteLine("No entries found matching the criteria.");
                return 0;
            }

            foreach (var result in results)
            {
                Console.WriteLine($"--- {result.Entry.Date} ---");
                Console.WriteLine(result.Entry.RawContent);
                Console.WriteLine();
            }

            return 0;
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"Search command failed: {ex.Message}");
            return 1;
        }
    }

    private static bool TryParseVaultOptions(string[] args, out VaultOptions options, out string error)
    {
        var parsed = new VaultOptions();
        for (var i = 0; i < args.Length; i++)
        {
            var token = args[i];
            if (IsHelp(token))
            {
                options = parsed;
                error = "";
                return false;
            }

            if (i + 1 >= args.Length)
            {
                options = parsed;
                error = $"Missing value for option '{token}'.";
                return false;
            }

            var value = args[i + 1];
            switch (token)
            {
                case "--password":
                case "-p":
                    parsed.Password = value;
                    break;
                case "--vault-dir":
                    parsed.VaultDirectory = value;
                    break;
                default:
                    options = parsed;
                    error = $"Unknown option '{token}'.";
                    return false;
            }

            i++;
        }

        options = parsed;
        error = "";
        return true;
    }

    private static bool TryParseSearchOptions(string[] args, out SearchOptions options, out string error)
    {
        var parsed = new SearchOptions();
        for (var i = 0; i < args.Length; i++)
        {
            var token = args[i];
            if (IsHelp(token))
            {
                options = parsed;
                error = "";
                return false;
            }

            if (!token.StartsWith('-'))
            {
                if (parsed.Query is null)
                {
                    parsed.Query = token;
                    continue;
                }

                options = parsed;
                error = $"Unexpected positional argument '{token}'.";
                return false;
            }

            if (i + 1 >= args.Length)
            {
                options = parsed;
                error = $"Missing value for option '{token}'.";
                return false;
            }

            var value = args[i + 1];
            switch (token)
            {
                case "--tag":
                case "-t":
                    parsed.Tags.Add(value);
                    break;
                case "--type":
                case "-y":
                    parsed.Types.Add(value);
                    break;
                case "--start-date":
                case "-s":
                    parsed.StartDate = value;
                    break;
                case "--end-date":
                case "-e":
                    parsed.EndDate = value;
                    break;
                case "--section":
                case "-sec":
                    parsed.Section = value;
                    break;
                case "--checked":
                case "-chk":
                    parsed.Checked.Add(value);
                    break;
                case "--unchecked":
                case "-uchk":
                    parsed.Unchecked.Add(value);
                    break;
                default:
                    options = parsed;
                    error = $"Unknown option '{token}'.";
                    return false;
            }

            i++;
        }

        options = parsed;
        error = "";
        return true;
    }

    private string ResolveVaultDirectory(string? vaultOverride)
    {
        var envVault = Environment.GetEnvironmentVariable("JOURNAL_VAULT_DIR");
        var defaults = _config.Current;

        var vault = FirstNonEmpty(vaultOverride, envVault) ?? defaults.VaultDirectory;
        return Path.GetFullPath(vault);
    }

    private string ResolveVaultStorageDirectory()
    {
        var dbDirOverride = Environment.GetEnvironmentVariable("JOURNAL_DATABASE_DIR");
        if (!string.IsNullOrWhiteSpace(dbDirOverride))
            return Path.GetFullPath(dbDirOverride);

        return Path.GetFullPath(Path.Combine(_config.Current.VaultDirectory, "db"));
    }

    private static string? FirstNonEmpty(params string?[] values) =>
        values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value));

    private static string PromptPassword()
    {
        if (Console.IsInputRedirected)
            return Console.ReadLine() ?? "";

        Console.Write("Vault password: ");
        var builder = new StringBuilder();
        while (true)
        {
            var keyInfo = Console.ReadKey(intercept: true);
            if (keyInfo.Key == ConsoleKey.Enter)
            {
                Console.WriteLine();
                break;
            }

            if (keyInfo.Key == ConsoleKey.Backspace)
            {
                if (builder.Length > 0)
                    builder.Length--;
                continue;
            }

            if (!char.IsControl(keyInfo.KeyChar))
                builder.Append(keyInfo.KeyChar);
        }

        return builder.ToString();
    }

    private static bool IsHelp(string token) =>
        string.Equals(token, "--help", StringComparison.OrdinalIgnoreCase) ||
        string.Equals(token, "-h", StringComparison.OrdinalIgnoreCase) ||
        string.Equals(token, "help", StringComparison.OrdinalIgnoreCase);

    private static void PrintUsage()
    {
        Console.WriteLine("Usage:");
        Console.WriteLine("  Journal.Sidecar               # sidecar stdin/stdout mode");
        Console.WriteLine("  Journal.Sidecar vault load [--password <value>] [--vault-dir <path>]");
        Console.WriteLine("  Journal.Sidecar vault save [--password <value>] [--vault-dir <path>]");
        Console.WriteLine("  Journal.Sidecar search [query] [--tag <value>] [--type <value>] [--start-date <yyyy-MM-dd>] [--end-date <yyyy-MM-dd>] [--section <title>] [--checked <text>] [--unchecked <text>]");
    }

    private static void PrintVaultUsage()
    {
        Console.WriteLine("Vault usage:");
        Console.WriteLine("  Journal.Sidecar vault load [--password <value>] [--vault-dir <path>]");
        Console.WriteLine("  Journal.Sidecar vault save [--password <value>] [--vault-dir <path>]");
    }

    private static void PrintSearchUsage()
    {
        Console.WriteLine("Search usage:");
        Console.WriteLine("  Journal.Sidecar search [query] [--tag <value>] [--type <value>] [--start-date <yyyy-MM-dd>] [--end-date <yyyy-MM-dd>] [--section <title>] [--checked <text>] [--unchecked <text>]");
    }

    private sealed class VaultOptions
    {
        public string? Password { get; set; }
        public string? VaultDirectory { get; set; }
    }

    private sealed class SearchOptions
    {
        public string? Query { get; set; }
        public string? StartDate { get; set; }
        public string? EndDate { get; set; }
        public string? Section { get; set; }
        public List<string> Tags { get; } = [];
        public List<string> Types { get; } = [];
        public List<string> Checked { get; } = [];
        public List<string> Unchecked { get; } = [];
    }
}
