using Application.Abstractions.Authentication;
using Application.Abstractions.Cache;
using Application.Abstractions.Data;
using Application.Helpers;
using Domain.Entities.Members.Logs;
using Application.Abstractions.Messaging;
using SharedKernel;
using SharedKernel.Results;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Application.Features.Api.Auth.Login;

internal sealed class Handler(
    IAppDbContext db,
    IJwtTokenProvider jwtTokenProvider,
    IOptions<AppSettings> options,
    ILogger<Handler> logger,
    ICacheService cache
) : ICommandHandler<Command, Result<Response>> {

    private readonly AppSettings.JwtSection _jwt = options.Value.JWT;

    public async Task<Result<Response>> Handle(Command request, CancellationToken ct)
    {
        // 유효성 검사
        if (string.IsNullOrWhiteSpace(request.Email))
        {
            return Result.Failure<Response>(Error.Problem("Auth.EmailRequired", "이메일은 필수입니다."));
        }

        if (string.IsNullOrWhiteSpace(request.Password))
        {
            return Result.Failure<Response>(Error.Problem("Auth.PasswordRequired", "비밀번호는 필수입니다."));
        }

        // Config 로드
        var accountConfig = await AccountConfigLoader.GetAccountConfigAsync(cache, db, ct);

        // Member 조회 (비밀번호 검증을 위해 Tracking 모드)
        var email = request.Email.Trim().ToLower();
        var member = await db.Member.FirstOrDefaultAsync(m => m.Email == email, ct);

        if (member is null)
        {
            // 실패 로그 기록 (회원 없음)
            await LogLoginAttempt(null, false, email, "회원 정보 없음", request, ct);
            return Result.Failure<Response>(Error.Unauthorized("Auth.InvalidCredentials", "이메일 또는 비밀번호가 올바르지 않습니다."));
        }

        // 탈퇴 회원 거부
        if (member.IsWithdraw)
        {
            await LogLoginAttempt(member.ID, false, email, "탈퇴 회원", request, ct);
            return Result.Failure<Response>(Error.Problem("Auth.MemberWithdrawn", "탈퇴한 회원은 이용할 수 없습니다."));
        }

        // 차단 회원 거부
        if (member.IsDenied)
        {
            await LogLoginAttempt(member.ID, false, email, "차단 회원", request, ct);
            return Result.Failure<Response>(Error.Problem("Auth.MemberDenied", "차단된 회원이므로 이용할 수 없습니다."));
        }

        // 로그인 시도 횟수 제한 확인
        if (accountConfig.MaxLoginTryCount is > 0 && accountConfig.MaxLoginTryLimitSecond is > 0)
        {
            var limitTime = DateTime.UtcNow.AddSeconds(-accountConfig.MaxLoginTryLimitSecond.Value);
            var failedCount = await db.MemberLoginLog.CountAsync(l => l.MemberID == member.ID && !l.Success && l.CreatedAt >= limitTime, ct);

            if (failedCount >= accountConfig.MaxLoginTryCount.Value)
            {
                await LogLoginAttempt(member.ID, false, email, "로그인 시도 횟수 초과", request, ct);
                return Result.Failure<Response>(Error.Problem("Auth.LoginTryExceeded", $"로그인 시도 횟수를 초과하였습니다. {accountConfig.MaxLoginTryLimitSecond}초 후에 다시 시도해주세요."));
            }
        }

        // 비밀번호 검증
        if (!member.VerifyPassword(request.Password))
        {
            await LogLoginAttempt(member.ID, false, email, "비밀번호 불일치", request, ct);
            return Result.Failure<Response>(Error.Unauthorized("Auth.InvalidCredentials", "이메일 또는 비밀번호가 올바르지 않습니다."));
        }

        // 로그인 시 이메일 인증자 여부 확인
        if (accountConfig.IsLoginEmailVerifiedOnly && !member.IsEmailVerified)
        {
            await LogLoginAttempt(member.ID, false, email, "이메일 미인증", request, ct);
            return Result.Failure<Response>(Error.Unauthorized("Auth.EmailNotVerified", "이메일 인증이 완료되지 않은 사용자는 로그인할 수 없습니다."));
        }

        // JWT 토큰 생성
        var accessToken = jwtTokenProvider.CreateAccessToken(member.ID, member.Email, member.Name);
        var refreshToken = jwtTokenProvider.CreateRefreshToken();
        var expiresAt = DateTime.UtcNow.AddMinutes(_jwt.AccessTokenExpiration);

        // RefreshToken 저장
        var refreshTokenEntity = Domain.Entities.Members.RefreshToken.Create(
            member.ID,
            refreshToken,
            DateTime.UtcNow.AddDays(_jwt.RefreshTokenExpiration)
        );

        await db.RefreshToken.AddAsync(refreshTokenEntity, ct);

        // 로그인 횟수 증가
        var memberStats = await db.MemberStats.FirstOrDefaultAsync(x => x.MemberID == member.ID, ct);
        if (memberStats is not null)
        {
            memberStats.LoginCount++;
        }

        // 성공 로그 기록
        await LogLoginAttempt(member.ID, true, email, null, request, ct);

        await db.SaveChangesAsync(ct);

        logger.LogInformation("{0} 로그인", member.Email);

        return Result.Success(new Response(accessToken, refreshToken, expiresAt));
    }

    private async Task LogLoginAttempt(int? memberID, bool success, string account, string? reason, Command request, CancellationToken ct)
    {
        var log = MemberLoginLog.Create(
            memberID,
            success,
            account,
            reason,
            ipAddress: request.IpAddress,
            userAgent: request.UserAgent
        );

        await db.MemberLoginLog.AddAsync(log, ct);
        await db.SaveChangesAsync(ct);
    }
}