using global::Android.App;
using global::Android.Content;
using global::Android.Content.PM;
using global::Android.OS;

namespace DodoSSH.Client.Android.Platform;

/// <summary>
/// Keeps the process alive for as long as a shell or a transfer is live.
/// </summary>
/// <remarks>
/// <para>
/// The decision recorded in docs/android-port.md: a persistent notification, for as long as there is
/// something running that would be wrong to kill. It costs the user a notification and some battery, and it
/// buys the behaviour the desktop client already promises and documents — that a shell outlives a vault
/// lock, and that a transfer finishes.
/// </para>
/// <para>
/// <b>Why this exists at all is worth stating plainly.</b> <c>TerminalWorkspace</c>'s guarantee is that
/// locking the vault does not close your shells, because the remote host never consulted the vault and the
/// credential was already spent. On a desktop that guarantee is free — the process keeps running. On
/// Android nothing keeps a backgrounded process running, so without this the guarantee would quietly become
/// desktop-only, and a phone would drop a shell the moment the user checked a message.
/// </para>
/// <para>
/// <b>It holds no state and owns nothing.</b> The sessions live in the composition root, exactly as they do
/// on the desktop; this only asks Android not to stop the process they are in. That is why starting and
/// stopping it is a count of live things rather than a lifecycle of its own — see
/// <see cref="SessionKeepAlive"/>.
/// </para>
/// </remarks>
[Service(
    Exported = false,

    // Android 14 (API 34) refuses to start a foreground service whose type is not declared both here and
    // in the manifest's permission list. dataSync is the type that matches: an SSH session and a file
    // transfer are both the user's data moving to somewhere the user chose.
    ForegroundServiceType = ForegroundService.TypeDataSync)]
internal sealed class SessionForegroundService : Service
{
    private const string ChannelId = "dodossh.sessions";
    private const int NotificationId = 1;

    /// <remarks>
    /// A bound service would tie the sessions' lifetime to a binding, which is the opposite of what is
    /// wanted here: the point is that they outlive whatever the user does with the interface.
    /// </remarks>
    public override IBinder? OnBind(Intent? intent) => null;

    public override StartCommandResult OnStartCommand(Intent? intent, StartCommandFlags flags, int startId)
    {
        StartForeground(NotificationId, BuildNotification(intent?.GetStringExtra("summary") ?? "Working"));

        // NotSticky: if Android does kill this process, the SSH connections died with it and there is
        // nothing to resume. Restarting the service would produce a notification claiming sessions that no
        // longer exist, which is exactly the kind of dishonest state the unlock screen's shell count exists
        // to prevent.
        return StartCommandResult.NotSticky;
    }

    /// <remarks>
    /// Low importance on purpose. This notification is a receipt, not an alert — it exists because Android
    /// requires one, and because the user is entitled to know the app is holding connections open. Making
    /// it buzz would be a notification about nothing having happened.
    /// </remarks>
    private Notification BuildNotification(string summary)
    {
        var manager = (NotificationManager)GetSystemService(NotificationService)!;

        if (OperatingSystem.IsAndroidVersionAtLeast(26))
        {
            var channel = new NotificationChannel(ChannelId, "Live sessions", NotificationImportance.Low)
            {
                Description = "Shown while a shell or a transfer is open.",
            };

            channel.SetShowBadge(false);
            manager.CreateNotificationChannel(channel);
        }

        var reopen = PendingIntent.GetActivity(
            this,
            0,
            new Intent(this, typeof(MainActivity)).SetFlags(ActivityFlags.SingleTop),
            PendingIntentFlags.Immutable | PendingIntentFlags.UpdateCurrent);

        return new Notification.Builder(this, ChannelId)
            .SetContentTitle("DodoSSH")
            .SetContentText(summary)
            .SetSmallIcon(global::Android.Resource.Drawable.IcDialogInfo)
            .SetContentIntent(reopen)
            .SetOngoing(true)!
            .Build();
    }

    /// <summary>Starts or stops the service to match what is actually running.</summary>
    /// <param name="liveSessions">Shells with a live channel behind them.</param>
    /// <param name="activeTransfers">Transfers still moving bytes.</param>
    public static void Reconcile(int liveSessions, int activeTransfers)
    {
        var context = PhoneEnvironment.Require();
        var intent = new Intent(context, typeof(SessionForegroundService));

        if (liveSessions == 0 && activeTransfers == 0)
        {
            context.StopService(intent);

            return;
        }

        // The summary says what is actually held, counted rather than generic — the same principle the
        // delete confirmations follow. "DodoSSH is running" would tell the user nothing they could act on.
        intent.PutExtra("summary", Summarise(liveSessions, activeTransfers));

        context.StartForegroundService(intent);
    }

    private static string Summarise(int liveSessions, int activeTransfers)
    {
        var parts = new List<string>(2);

        if (liveSessions > 0)
        {
            parts.Add(liveSessions == 1 ? "1 shell connected" : $"{liveSessions} shells connected");
        }

        if (activeTransfers > 0)
        {
            parts.Add(activeTransfers == 1 ? "1 transfer running" : $"{activeTransfers} transfers running");
        }

        return string.Join(" · ", parts);
    }
}
