using DodoSSH.Client.Api;
using DodoSSH.Client.Auth;
using DodoSSH.Client.Session;
using DodoSSH.Client.Sync;
using DodoSSH.Contracts;

namespace DodoSSH.Client.App.Layout.Tests;

/// <summary>
/// The least server a <c>VaultsViewModel</c> needs in order to be laid out with something in it.
/// </summary>
/// <remarks>
/// <para>
/// The vaults screen draws its list from the session and everything under it from the server: who is in a
/// vault and who holds a key are both read on open, and the suite's
/// <c>FakeAccountServer</c> implements <see cref="IAccountApi"/> and nothing else. Rather than teach that
/// fake five more interfaces for one screen, this serves fixed rows and refuses everything a layout test
/// has no business calling.
/// </para>
/// <para>
/// It does answer <see cref="CreateTeamVaultAsync"/>, unlike the other writes, because that is how the
/// suite gets a shared vault into the session at all — an offline layout test has no other way to reach
/// the state this screen exists to draw.
/// </para>
/// <para>
/// The rows are deliberately the <em>long</em> ones. A layout suite that measured "Bob" in a column sized
/// for an email address would certify a shape no real team produces — so the names, addresses and status
/// sentences here are at or near the length the interface can really be handed, which is what makes an
/// overflow show up at the minimum window rather than on somebody's screen.
/// </para>
/// </remarks>
internal sealed class StubTeamServer : IVaultServer, ITeamApi, IVaultGrantApi
{
    private static readonly Guid OwnerId = Guid.CreateVersion7();
    private static readonly Guid ColleagueId = Guid.CreateVersion7();
    private static readonly Guid TeamId = Guid.CreateVersion7();
    private static readonly Guid VaultId = Guid.CreateVersion7();

    /// <inheritdoc />
    public Uri ServerUrl { get; } = new("https://dodossh.example");

    /// <inheritdoc />
    public ITeamApi Teams => this;

    /// <inheritdoc />
    public IVaultGrantApi Grants => this;

    /// <inheritdoc />
    public IAccountApi Account => throw new NotSupportedException();

    /// <inheritdoc />
    public ISyncApi Sync => throw new NotSupportedException();

    /// <inheritdoc />
    public IDirectoryApi Directory => throw new NotSupportedException();

    /// <inheritdoc />
    public IKeyBindingAuthorizer KeyBinding => throw new NotSupportedException();

    /// <summary>
    /// A push channel that never pushes.
    /// </summary>
    /// <remarks>
    /// Not <c>NotSupportedException</c> like its neighbours: the background synchronisation loop reads
    /// this on every wait, so a layout test that opened a screen would throw from a timer thread rather
    /// than draw anything. Waiting for ever is the honest stand-in — an offline layout test has no
    /// server to be pushed from.
    /// </remarks>
    public IVaultEventStream Events => IdleVaultEventStream.Instance;

    /// <inheritdoc />
    public SyncOptions SyncOptions => new();

    /// <inheritdoc />
    public string? RefreshToken => null;

    /// <summary>The vault whose key holders are listed, so a test can select it.</summary>
    internal static Guid TeamVaultId => VaultId;

    /// <summary>The membership list behind that vault, so a test can create it in the session.</summary>
    internal static Guid SharedTeamId => TeamId;

    /// <inheritdoc />
    public Task<IReadOnlyList<TeamSummary>> ListTeamsAsync(CancellationToken cancellationToken) =>
        Task.FromResult<IReadOnlyList<TeamSummary>>(
        [
            new TeamSummary(
                TeamId,
                "Platform Engineering",
                "platform-engineering",
                "Everything that runs the estate.",
                TeamMemberRole.Owner,
                MemberCount: 2,
                VaultCount: 1,
                DateTimeOffset.UnixEpoch),
        ]);

    /// <inheritdoc />
    public Task<IReadOnlyList<TeamMemberSummary>> ListTeamMembersAsync(
        Guid teamId,
        CancellationToken cancellationToken) =>
        Task.FromResult<IReadOnlyList<TeamMemberSummary>>(
        [
            new TeamMemberSummary(
                OwnerId,
                "alexandra.hollingsworth@dodotech.example",
                "Alexandra Hollingsworth",
                TeamMemberRole.Owner,
                TeamMemberStatus.Active,
                IsEnrolled: true,
                DateTimeOffset.UnixEpoch,
                DateTimeOffset.UnixEpoch),

            // The unenrolled case on purpose: its key-state phrase is the longest the column ever
            // carries, and it is the row that decides whether that column is wide enough.
            new TeamMemberSummary(
                ColleagueId,
                "bartholomew.fotheringay@dodotech.example",
                "Bartholomew Fotheringay",
                TeamMemberRole.Member,
                TeamMemberStatus.Active,
                IsEnrolled: false,
                DateTimeOffset.UnixEpoch,
                LastActiveAt: null),
        ]);

    /// <inheritdoc />
    public Task<VaultGrantsResponse> ListVaultGrantsAsync(
        Guid vaultId,
        CancellationToken cancellationToken) =>
        Task.FromResult(new VaultGrantsResponse(
            vaultId,
            KeyGeneration: 2,
            RekeyRequired: true,
            Grants:
            [
                new VaultGrantSummary(
                    OwnerId,
                    "alexandra.hollingsworth@dodotech.example",
                    "Alexandra Hollingsworth",
                    KeyGeneration: 2,
                    VaultGrantState.Active,
                    OwnerId,
                    DateTimeOffset.UnixEpoch,
                    RevokedAt: null),

                // A generation behind, so the "stale" phrasing is the one being measured rather than
                // the two-word happy case.
                new VaultGrantSummary(
                    ColleagueId,
                    "bartholomew.fotheringay@dodotech.example",
                    "Bartholomew Fotheringay",
                    KeyGeneration: 1,
                    VaultGrantState.Active,
                    OwnerId,
                    DateTimeOffset.UnixEpoch,
                    RevokedAt: null),
            ]));

    /// <inheritdoc />
    public Task<TeamSummary> CreateTeamAsync(
        CreateTeamRequest request,
        CancellationToken cancellationToken) => throw new NotSupportedException();

    /// <inheritdoc />
    public Task<TeamSummary> UpdateTeamAsync(
        Guid teamId,
        UpdateTeamRequest request,
        CancellationToken cancellationToken) => throw new NotSupportedException();

    /// <inheritdoc />
    public Task<bool> ArchiveTeamAsync(Guid teamId, CancellationToken cancellationToken) =>
        throw new NotSupportedException();

    /// <inheritdoc />
    public Task TransferTeamOwnershipAsync(
        Guid teamId,
        TransferTeamOwnershipRequest request,
        CancellationToken cancellationToken) => throw new NotSupportedException();

    /// <inheritdoc />
    public Task<TeamMemberSummary> AddTeamMemberAsync(
        Guid teamId,
        AddTeamMemberRequest request,
        CancellationToken cancellationToken) => throw new NotSupportedException();

    /// <inheritdoc />
    public Task<TeamMemberSummary> ChangeTeamMemberRoleAsync(
        Guid teamId,
        Guid userId,
        ChangeTeamMemberRoleRequest request,
        CancellationToken cancellationToken) => throw new NotSupportedException();

    /// <inheritdoc />
    public Task<bool> RemoveTeamMemberAsync(
        Guid teamId,
        Guid userId,
        CancellationToken cancellationToken) => throw new NotSupportedException();

    /// <summary>
    /// Accepts the vault, so a layout test can put a shared one into the session it is drawing.
    /// </summary>
    /// <remarks>
    /// The client's own id and wrapped key are echoed back, exactly as the real endpoint answers: the key
    /// was generated on this machine and the session adopts its own copy, so anything else here would be
    /// either discarded or a vault nobody could open.
    /// <para>
    /// It comes back owing a rekey, which is not decoration: that is the longer of the two lines a vault row
    /// can carry, and this suite exists to measure the long one.
    /// </para>
    /// </remarks>
    public Task<VaultSummary> CreateTeamVaultAsync(
        Guid teamId,
        CreateTeamVaultRequest request,
        CancellationToken cancellationToken) =>
        Task.FromResult(new VaultSummary(
            request.VaultId,
            request.Name,
            IsPersonal: false,
            TeamId: teamId,
            KeyGeneration: 1,
            Permissions: 31,
            request.WrappedVaultKey,
            RekeyRequired: true));

    /// <inheritdoc />
    public Task<VaultSummary> RenameVaultAsync(
        Guid vaultId,
        UpdateVaultRequest request,
        CancellationToken cancellationToken) => throw new NotSupportedException();

    /// <inheritdoc />
    public Task<bool> DeleteVaultAsync(Guid vaultId, CancellationToken cancellationToken) =>
        throw new NotSupportedException();

    /// <inheritdoc />
    public Task IssueVaultGrantAsync(
        Guid vaultId,
        IssueVaultGrantRequest request,
        CancellationToken cancellationToken) => throw new NotSupportedException();

    /// <inheritdoc />
    public Task<bool> RevokeVaultGrantAsync(
        Guid vaultId,
        Guid userId,
        CancellationToken cancellationToken) => throw new NotSupportedException();

    /// <inheritdoc />
    public Task<VaultSummary> RekeyVaultAsync(
        Guid vaultId,
        RekeyVaultRequest request,
        CancellationToken cancellationToken) => throw new NotSupportedException();

    /// <inheritdoc />
    public void Dispose()
    {
        // Nothing held.
    }
}
