using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using bitforum.Models.Account;
using bitforum.Models;
using bitforum.Repository;

namespace bitforum.Services
{
    public interface IJwtAuthService
    {
        // Access Token 생성
        string GenerateAccessToken(Member member);

        // Refresh Token 생성
        string GenerateRefreshToken();

        // Access Token 검증
        bool ValidateAccessToken(string accessToken);

        // Refresh Token 검증
        Task<bool> ValidateRefreshToken(string refreshToken);

        // Refresh Token DB 저장
        Task SaveRefreshToken(int memberID, string refreshToken);

        // AccessToken에서 회원 ID 추출
        Member? GetMemberFromAccessToken(string accessToken);

        // RefreshToken에서 회원 ID 추출
        Task<Member?> GetMemberFromRefreshToken(string refreshToken);

        // AccessToken 만료 기간 조회
        DateTime GetAccessTokenExpiration();

        // RefreshToken 만료 기간 조회
        DateTime GetRefreshTokenExpiration();

        // AccessToken 설정 및 쿠키 설정
        string SetAccessTokenAndCookieAsync(Member member);

        // RefreshToken 설정 및 쿠키 설정
        Task<string> SetRefreshTokenAndCookieAsync(Member member);
    }

    public class JwtAuthService : IJwtAuthService
    {
        private readonly DefaultDbContext _db;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IMemoryCache _cache;
        private readonly IRedisRepository _redisRepository;
        private readonly string _secretKey;
        private readonly string _issuer;
        private readonly string _audience;
        private readonly int _accessTokenExpiration;
        private readonly int _refreshTokenExpiration;

        public JwtAuthService(DefaultDbContext db, IHttpContextAccessor httpContextAccessor, IMemoryCache cache, IRedisRepository redisRepository)
        {
            _db = db;
            _httpContextAccessor = httpContextAccessor;
            _cache = cache;
            _redisRepository = redisRepository;
            _secretKey = Setting.Jwt.SecretKey;
            _issuer = Setting.Jwt.Issuer;
            _audience = Setting.Jwt.Audience;
            _accessTokenExpiration = Setting.Jwt.AccessTokenExpiration;
            _refreshTokenExpiration = Setting.Jwt.RefreshTokenExpiration;
        }

        public string GenerateAccessToken(Member member)
        {
            var claims = new[]
            {
                new Claim(ClaimTypes.NameIdentifier, member.ID.ToString()),
                new Claim(ClaimTypes.Email, member.Email),
                new Claim(ClaimTypes.Name, member.Name ?? "")
            };

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(
                issuer: _issuer,
                audience: _audience,
                claims: claims,
                expires: GetAccessTokenExpiration(),
                signingCredentials: creds
            );

            return new JwtSecurityTokenHandler().WriteToken(token);
        }

        public string GenerateRefreshToken()
        {
            return Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));
        }

        public bool ValidateAccessToken(string accessToken)
        {
            // 캐싱된 검증 결과 사용 (속도 향상)
            if (_cache.TryGetValue($"valid_token_{accessToken}", out bool isValid))
            {
                return isValid;
            }

            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.UTF8.GetBytes(_secretKey);
                var principal = tokenHandler.ValidateToken(accessToken, new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidIssuer = _issuer,
                    ValidAudience = _audience,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.Zero
                }, out SecurityToken validatedToken);

                // 검증된 토큰을 캐싱하여 속도 향상 (만료 시간의 절반으로 유지)
                _cache.Set($"valid_token_{accessToken}", true, TimeSpan.FromMinutes(_accessTokenExpiration / 2));

                return true;
            }
            catch (SecurityTokenExpiredException)
            {
                return false;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public async Task<bool> ValidateRefreshToken(string refreshToken)
        {
            // 속도를 위해 Redis에서 RefreshToken 검증
            return await _redisRepository.ExistsAsync($"refresh_token_{refreshToken}");

            // DB에서 RefreshToken 검증
            // return await _db.RefreshToken.AnyAsync(c => c.Token == refreshToken && c.Expiration > DateTime.UtcNow);
        }

        public async Task SaveRefreshToken(int memberID, string refreshToken)
        {
            await _redisRepository.SetStringAsync($"refresh_token_{refreshToken}", memberID.ToString(), _refreshTokenExpiration);

            var expiration = DateTime.UtcNow.AddDays(_refreshTokenExpiration);
            var existingToken = await _db.RefreshToken.FirstOrDefaultAsync(c => c.MemberID == memberID);
            if (existingToken != null)
            {
                if (existingToken.Token == refreshToken)
                {
                    return;
                }

                existingToken.Token = refreshToken;
                existingToken.Expiration = expiration;
            }
            else
            {
                _db.RefreshToken.Add(new RefreshToken
                {
                    MemberID = memberID,
                    Token = refreshToken,
                    Expiration = expiration
                });
            }

            // 만료된 RefreshToken 정리
            _db.RefreshToken.RemoveRange(_db.RefreshToken.Where(c => c.Expiration < DateTime.UtcNow));

            await _db.SaveChangesAsync();
        }

        public Member? GetMemberFromAccessToken(string accessToken)
        {
            var jwtToken = new JwtSecurityTokenHandler().ReadToken(accessToken) as JwtSecurityToken;
            if (jwtToken == null)
            {
                return null;
            }

            var memberIDClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
            var emailClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);

            if (memberIDClaim == null || emailClaim == null)
            {
                return null;
            }

            return new Member
            {
                ID = int.Parse(memberIDClaim.Value),
                Email = emailClaim.Value
            };
        }

        public async Task<Member?> GetMemberFromRefreshToken(string refreshToken)
        {
            var memberID = await _db.RefreshToken.Where(rt => rt.Token == refreshToken && rt.Expiration > DateTime.UtcNow).Select(c => c.MemberID).FirstOrDefaultAsync();
            if (memberID == 0)
            {
                return null;
            }

            return await _db.Member.FindAsync(memberID);
        }

        public DateTime GetAccessTokenExpiration()
        {
            return DateTime.UtcNow.AddMinutes(_accessTokenExpiration);
        }

        public DateTime GetRefreshTokenExpiration()
        {
            return DateTime.UtcNow.AddDays(_refreshTokenExpiration);
        }

        public string SetAccessTokenAndCookieAsync(Member member)
        {
            var accessToken = GenerateAccessToken(member);

            var cookies = _httpContextAccessor.HttpContext?.Response.Cookies;
            var headers = _httpContextAccessor.HttpContext?.Response.Headers;

            // AccessToken을 HttpOnly 쿠키로 설정
            if (cookies != null)
            {
                cookies.Append("accessToken", accessToken, new CookieOptions
                {
                    HttpOnly = true,
                    Secure = true,
                    SameSite = SameSiteMode.None,
                    Expires = GetAccessTokenExpiration()
                });
            }

            if (headers != null)
            {
                headers["Authorization"] = $"Bearer {accessToken}";
            }

            return accessToken;
        }

        public async Task<string> SetRefreshTokenAndCookieAsync(Member member)
        {
            var refreshToken = GenerateRefreshToken();
            
            await SaveRefreshToken(member.ID, refreshToken);

            var cookies = _httpContextAccessor.HttpContext?.Response.Cookies;

            // RefreshToken을 HttpOnly 쿠키로 설정
            if (cookies != null)
            {
                cookies.Append("refreshToken", refreshToken, new CookieOptions
                {
                    HttpOnly = true,
                    Secure = true,
                    SameSite = SameSiteMode.None,
                    Expires = GetRefreshTokenExpiration()
                });
            }
            return refreshToken;
        }
    }
}
