using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Geometry;
using DysonNetwork.Shared.Proto;
using NodaTime;
using NodaTime.Serialization.Protobuf;

namespace DysonNetwork.Shared.Models;

public enum SessionType
{
    Login,
    OAuth, // Trying to authorize other platforms
    Oidc // Trying to connect other platforms
}

public class SnAuthSession : ModelBase
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public SessionType Type { get; set; } = SessionType.Login;
    public Instant? LastGrantedAt { get; set; }
    public Instant? ExpiredAt { get; set; }
    
    [Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = [];
    [Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = [];
    [MaxLength(128)] public string? IpAddress { get; set; }
    [MaxLength(512)] public string? UserAgent { get; set; }
    [Column(TypeName = "jsonb")] public GeoPoint? Location { get; set; }

    public Guid AccountId { get; set; }
    [JsonIgnore] public SnAccount Account { get; set; } = null!;

    // The client device for this session
    public Guid? ClientId { get; set; }
    public SnAuthClient? Client { get; set; } = null!;
    
    // For sub-sessions (e.g. OAuth)
    public Guid? ParentSessionId { get; set; }
    public SnAuthSession? ParentSession { get; set; }

    // The origin challenge for this session
    public Guid? ChallengeId { get; set; }

    // Indicates the session is for an OIDC connection
    public Guid? AppId { get; set; }

    public AuthSession ToProtoValue()
    {
        var proto = new AuthSession
        {
            Id = Id.ToString(),
            LastGrantedAt = LastGrantedAt?.ToTimestamp(),
            Type = Type switch
            {
                SessionType.Login => Proto.SessionType.Login,
                SessionType.OAuth => Proto.SessionType.Oauth,
                SessionType.Oidc => Proto.SessionType.Oidc,
                _ => Proto.SessionType.ChallengeTypeUnspecified
            },
            IpAddress = IpAddress,
            UserAgent = UserAgent,
            ExpiredAt = ExpiredAt?.ToTimestamp(),
            AccountId = AccountId.ToString(),
            Account = Account.ToProtoValue(),
            ClientId = ClientId.ToString(),
            Client = Client?.ToProtoValue(),
            ParentSessionId = ParentSessionId.ToString(),
            AppId = AppId?.ToString()
        };
        
        proto.Audiences.AddRange(Audiences);
        proto.Scopes.AddRange(Scopes);

        return proto;
    }
}

public enum ClientPlatform
{
    Unidentified,
    Web,
    Ios,
    Android,
    MacOs,
    Windows,
    Linux
}

public class SnAuthChallenge : ModelBase
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public Instant? ExpiredAt { get; set; }
    public int StepRemain { get; set; }
    public int StepTotal { get; set; }
    public int FailedAttempts { get; set; }
    [Column(TypeName = "jsonb")] public List<Guid> BlacklistFactors { get; set; } = [];
    [Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = [];
    [Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = [];
    [MaxLength(128)] public string? IpAddress { get; set; }
    [MaxLength(512)] public string? UserAgent { get; set; }
    [MaxLength(512)] public string DeviceId { get; set; } = null!;
    [MaxLength(1024)] public string? DeviceName { get; set; }
    public ClientPlatform Platform { get; set; }
    [MaxLength(1024)] public string? Nonce { get; set; }
    [Column(TypeName = "jsonb")] public GeoPoint? Location { get; set; }

    public Guid AccountId { get; set; }
    [JsonIgnore] public SnAccount Account { get; set; } = null!;

    public SnAuthChallenge Normalize()
    {
        if (StepRemain == 0 && BlacklistFactors.Count == 0) StepRemain = StepTotal;
        return this;
    }

    public AuthChallenge ToProtoValue() => new()
    {
        Id = Id.ToString(),
        ExpiredAt = ExpiredAt?.ToTimestamp(),
        StepRemain = StepRemain,
        StepTotal = StepTotal,
        FailedAttempts = FailedAttempts,
        BlacklistFactors = { BlacklistFactors.Select(x => x.ToString()) },
        Audiences = { Audiences },
        Scopes = { Scopes },
        IpAddress = IpAddress,
        UserAgent = UserAgent,
        DeviceId = DeviceId,
        Nonce = Nonce,
        AccountId = AccountId.ToString()
    };
}

public class SnAuthClient : ModelBase
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public ClientPlatform Platform { get; set; } = ClientPlatform.Unidentified;
    [MaxLength(1024)] public string DeviceName { get; set; } = string.Empty;
    [MaxLength(1024)] public string? DeviceLabel { get; set; }
    [MaxLength(1024)] public string DeviceId { get; set; } = string.Empty;

    public Guid AccountId { get; set; }
    [JsonIgnore] public SnAccount Account { get; set; } = null!;
    
    public Proto.AuthClient ToProtoValue() => new()
    {
        Id = Id.ToString(),
        Platform = (Proto.ClientPlatform)Platform,
        DeviceName = DeviceName,
        DeviceLabel = DeviceLabel,
        DeviceId = DeviceId,
        AccountId = AccountId.ToString()
    };
}

public class SnAuthClientWithSessions : SnAuthClient
{
    public List<SnAuthSession> Sessions { get; set; } = [];

    public static SnAuthClientWithSessions FromClient(SnAuthClient client)
    {
        return new SnAuthClientWithSessions
        {
            Id = client.Id,
            Platform = client.Platform,
            DeviceName = client.DeviceName,
            DeviceLabel = client.DeviceLabel,
            DeviceId = client.DeviceId,
            AccountId = client.AccountId,
        };
    }
}
