using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Security.Cryptography;
using Domain.Entities.Members.ValueObject;
using Domain.Entities.Wallets;
using NanoidDotNet;

/// <summary>
/// 사용자단 일반 회원 정보
/// </summary>
namespace Domain.Entities.Members
{
    public class Member
    {
        [Key]
        public int ID { get; private set; }

        // 회원 등급 정보
        [ForeignKey(nameof(MemberGradeID))]
        public virtual MemberGrade? MemberGrade { get; private set; }

        // 알림/약관 동의 여부
        public virtual MemberApprove MemberApprove { get; private set; } = null!;

        // 회원 집계 정보
        public virtual MemberStats MemberStats { get; private set; } = null!;

        // 지갑
        public virtual Wallet? Wallet { get; private set; }

        // 채널
        public virtual Channel? Channel { get; private set; }

        public int? MemberGradeID { get; private set; }

        public string SID { get; private set; } = Nanoid.Generate(size: 8); // 무작위 문자열

        public string Email { get; private set; } = default!;

        public string? Name { get; private set; }

        public string? FullName { get; private set; }

        public string? FirstName { get; private set; }

        public string? LastName { get; private set; }

        public string? Password { get; private set; } = string.Empty; // 관리자 수정 시 필요

        public string? PasswordHash { get; private set; }

        public string? Intro { get; private set; }

        public string? Summary { get; private set; }

        public string? Phone { get; private set; }

        public DateOnly? Birthday { get; private set; }

        public Gender? Gender { get; private set; }

        public string? Thumb { get; private set; }

        public string? Icon { get; private set; }

        public bool IsEmailVerified { get; private set; } = false;

        public bool IsAuthCertified { get; private set; } = false;

        public bool IsDenied { get; private set; } = false;

        public bool IsAdmin { get; private set; } = false;

        public bool IsWithdraw { get; private set; } = false;

        public bool IsCreator { get; private set; } = false;

        public string? DeviceInfo { get; private set; }

        public string? SignupIP { get; private set; }

        public string? LastLoginIp { get; private set; }

        public string? IpAddress { get; private set; }

        public string? UserAgent { get; private set; }

        public DateTime? LastLoginAt { get; private set; }

        public DateTime? LastEmailChangedAt { get; private set; }

        public DateTime? LastNameChangedAt { get; private set; }

        public DateTime? LastSummaryChangedAt { get; private set; }

        public DateTime? LastIntroChangedAt { get; private set; }

        public DateTime? EmailVerifiedAt { get; private set; }

        public DateTime? AuthCertifiedAt { get; private set; }

        public DateTime PasswordUpdatedAt { get; private set; }

        public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;

        public DateTime? UpdatedAt { get; private set; }

        public DateTime? DeletedAt { get; private set; }

        public DateTime? DeniedAt { get; private set; }

        private Member() { }

        private Member(string email)
        {
            if (string.IsNullOrWhiteSpace(email))
            {
                throw new ArgumentException("Email is required.", nameof(email));
            }

            if (email.Length > 60)
            {
                throw new ArgumentOutOfRangeException(nameof(email));
            }

            Email = email;
            PasswordUpdatedAt = DateTime.UtcNow;
        }

        public static Member Create(string email)
        {
            return new(email);
        }

        public static Member Create(string email, string password)
        {
            var member = new Member(email);
            member.SetPassword(password);
            return member;
        }

        public void SetPassword(string password)
        {
            PasswordHash = HashPassword(password);
            PasswordUpdatedAt = DateTime.UtcNow;
        }

        public bool VerifyPassword(string password)
        {
            if (string.IsNullOrEmpty(PasswordHash))
            {
                return false;
            }

            return VerifyHashedPassword(PasswordHash, password);
        }

        public void SetEmail(string email)
        {
            Email = email;
            IsEmailVerified = false;
            LastEmailChangedAt = DateTime.UtcNow;
            UpdatedAt = DateTime.UtcNow;
        }

        public void SetName(string? name)
        {
            Name = name;
            LastNameChangedAt = DateTime.UtcNow;
            UpdatedAt = DateTime.UtcNow;
        }

        public void SetSummary(string? summary)
        {
            Summary = summary;
            LastSummaryChangedAt = DateTime.UtcNow;
            UpdatedAt = DateTime.UtcNow;
        }

        public void SetIntro(string? intro)
        {
            Intro = intro;
            LastIntroChangedAt = DateTime.UtcNow;
            UpdatedAt = DateTime.UtcNow;
        }

        public void SetThumb(string? thumb)
        {
            Thumb = thumb;
            UpdatedAt = DateTime.UtcNow;
        }

        public void MarkEmailVerified()
        {
            IsEmailVerified = true;
            EmailVerifiedAt = DateTime.UtcNow;
            UpdatedAt = DateTime.UtcNow;
        }

        public void Withdraw()
        {
            IsWithdraw = true;
            DeletedAt = DateTime.UtcNow;
            UpdatedAt = DateTime.UtcNow;
        }

        /// <summary>회원 등급 설정 — 보상 엔진 자동 승급(래칫, 강등 없음)에서 사용.</summary>
        public void SetMemberGrade(int memberGradeID)
        {
            MemberGradeID = memberGradeID;
            UpdatedAt = DateTime.UtcNow;
        }

        /// <summary>로그인 시각/IP/UA 기록. DB 컬럼 길이 방어(비정상 길이는 throw 대신 truncate).</summary>
        public void RecordLogin(string? ipAddress, string? userAgent = null)
        {
            if (ipAddress is not null && ipAddress.Length > 45)
            {
                ipAddress = ipAddress[..45];
            }

            if (userAgent is not null && userAgent.Length > 255)
            {
                userAgent = userAgent[..255];
            }

            LastLoginAt = DateTime.UtcNow;
            LastLoginIp = ipAddress;

            if (!string.IsNullOrWhiteSpace(ipAddress))
            {
                IpAddress = ipAddress;
            }

            if (!string.IsNullOrWhiteSpace(userAgent))
            {
                UserAgent = userAgent;
            }

            UpdatedAt = DateTime.UtcNow;
        }

        // PBKDF2 (RFC 2898) - 순수 C# 비밀번호 해싱
        // public: Admin 회원 생성/수정의 bulk ExecuteUpdate 에서 평문 대신 해시를 저장하기 위해 노출
        public static string HashPassword(string password)
        {
            var salt = RandomNumberGenerator.GetBytes(16);
            var hash = Rfc2898DeriveBytes.Pbkdf2(
                password,
                salt,
                iterations: 100_000,
                HashAlgorithmName.SHA256,
                outputLength: 32
            );

            // salt + hash → Base64
            var combined = new byte[salt.Length + hash.Length];
            Buffer.BlockCopy(salt, 0, combined, 0, salt.Length);
            Buffer.BlockCopy(hash, 0, combined, salt.Length, hash.Length);

            return Convert.ToBase64String(combined);
        }

        // 비밀번호 검증
        private static bool VerifyHashedPassword(string hashedPassword, string password)
        {
            var combined = Convert.FromBase64String(hashedPassword);

            if (combined.Length != 48) // 16(salt) + 32(hash)
            {
                return false;
            }

            var salt = combined[..16];
            var storedHash = combined[16..];

            var hash = Rfc2898DeriveBytes.Pbkdf2(
                password,
                salt,
                iterations: 100_000,
                HashAlgorithmName.SHA256,
                outputLength: 32
            );

            return CryptographicOperations.FixedTimeEquals(storedHash, hash);
        }
    }
}