using DodoSSH.Client.Domain;

namespace DodoSSH.Client.Import;

/// <summary>
/// One host an <c>ssh_config</c> describes, resolved and ready to be looked at.
/// </summary>
/// <remarks>
/// Deliberately not a <see cref="HostSecret"/>. This is a candidate somebody has not agreed to import yet,
/// and it carries things a stored host has no field for — the identity file's path, the jump alias by name,
/// and the warnings that go beside a row in the preview.
/// </remarks>
/// <param name="Alias">
/// The name from the <c>Host</c> line, which is what the user types after <c>ssh</c> and so the name they
/// will recognise.
/// </param>
/// <param name="Hostname">
/// What <c>HostName</c> said, or the alias when it said nothing — which is OpenSSH's own default and the
/// reason <c>Host db.internal</c> with no other directive works.
/// </param>
/// <param name="Username">What <c>User</c> said, if anything.</param>
/// <param name="Port">What <c>Port</c> said, defaulting to 22.</param>
/// <param name="IdentityFiles">Every <c>IdentityFile</c> path, in the order they were given.</param>
/// <param name="ProxyJump">The <c>ProxyJump</c> value verbatim, if any.</param>
/// <param name="Options">Everything else, as SSH directives.</param>
/// <param name="Warnings">What could not be represented, per host.</param>
public sealed record ImportedHost(
    string Alias,
    string Hostname,
    string? Username,
    int Port,
    IReadOnlyList<string> IdentityFiles,
    string? ProxyJump,
    HostOptions Options,
    IReadOnlyList<string> Warnings)
{
    /// <summary>The address this would dial, for a preview row.</summary>
    public string Address => Username is { Length: > 0 } user
        ? $"{user}@{Hostname}:{Port}"
        : $"{Hostname}:{Port}";

    /// <summary>Turns this into the host that would be stored.</summary>
    /// <remarks>
    /// <para>
    /// <b>The identity file becomes a note and a directive, not a key — here.</b> Reading somebody's
    /// <c>~/.ssh/id_ed25519</c> into a keychain is exactly the act this product exists to make deliberate, so
    /// nothing on this path does it: the value is recorded so it is not lost, and this record has no way to
    /// reach a disk in any case.
    /// <para>
    /// ◆ The material <em>can</em> be imported, and the tick that does it is on the import screen, off by
    /// default and beside a sentence saying what it will read. It runs through <see cref="SshIdentityFiles"/>
    /// and binds the stored key to the host afterwards; what it does not do is change what this method
    /// produces, so a host imported without it and a host imported with it differ by a binding rather than
    /// by their contents.
    /// </para>
    /// </para>
    /// <para>
    /// <b>ProxyJump records intent and changes nothing about connecting.</b> The SSH layer has no jump
    /// hosts — <c>ISshConnection</c> offers <c>OpenShellAsync</c> and nothing else, and
    /// <c>SshConnectionRequest</c> has no route field. So it is kept as a directive and a note, and the
    /// preview says so; a bastion topology that imported and quietly did not route would be worse than one
    /// that was not imported.
    /// </para>
    /// </remarks>
    public HostSecret ToSecret()
    {
        var options = new List<HostOption>(Options);
        var notes = new List<string>();

        if (IdentityFiles.Count > 0)
        {
            options.Add(new HostOption("IdentityFile", IdentityFiles[0]));

            notes.Add(IdentityFiles.Count == 1
                ? $"ssh_config used the key at {IdentityFiles[0]}."
                : $"ssh_config listed {IdentityFiles.Count} keys, the first being {IdentityFiles[0]}.");
        }

        if (ProxyJump is { Length: > 0 } jump)
        {
            options.Add(new HostOption("ProxyJump", jump));
            notes.Add($"ssh_config reached this through {jump}. DodoSSH does not route through a jump host yet.");
        }

        return new HostSecret
        {
            Label = Alias,
            Hostname = Hostname,
            Port = Port,
            Username = Username,
            Notes = notes.Count == 0 ? null : string.Join(" ", notes),
            Options = HostOptions.Create(options),
        };
    }
}

/// <summary>
/// Everything an <c>ssh_config</c> yielded: the hosts it can offer, and what it could not.
/// </summary>
/// <param name="Hosts">The importable candidates, in file order.</param>
/// <param name="SkippedPatterns">
/// <c>Host</c> patterns that are patterns rather than names. They contribute defaults and are not
/// importable: a bookmark called <c>*.internal</c> is one nothing can dial.
/// </param>
/// <param name="Warnings">Document-level notes, including the parser's own.</param>
public sealed record SshConfigImport(
    IReadOnlyList<ImportedHost> Hosts,
    IReadOnlyList<string> SkippedPatterns,
    IReadOnlyList<string> Warnings);
