using Application.Abstractions.Data;
using Application.Abstractions.Cache;
using Application.Abstractions.Messaging;
using Application.Abstractions.Messaging.Email;
using Application.Helpers;
using Domain.Entities.EmailVerification;
using Domain.Entities.EmailVerification.ValueObject;
using SharedKernel;
using SharedKernel.Results;
using Domain.Entities.Members.Logs;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System.Security.Cryptography;

namespace Application.Features.Api.MyPage.ChangeEmail;

internal sealed class Handler(
    IAppDbContext db,
    ICacheService cache,
    IMailService mailService,
    IOptions<AppSettings> options
) : ICommandHandler<Command, Result> {

    public async Task<Result> Handle(Command request, CancellationToken ct)
    {
        var newEmail = request.NewEmail?.Trim().ToLower();

        if (string.IsNullOrWhiteSpace(newEmail))
        {
            return Result.Failure(Error.Problem("MyPage.EmailRequired", "이메일은 필수입니다."));
        }

        if (newEmail.Length > 60)
        {
            return Result.Failure(Error.Problem("MyPage.EmailTooLong", "이메일은 60자 이하여야 합니다."));
        }

        var member = await db.Member.FirstOrDefaultAsync(m => m.ID == request.MemberID, ct);
        if (member is null)
        {
            return Result.Failure(Error.NotFound("MyPage.MemberNotFound", "회원 정보를 찾을 수 없습니다."));
        }

        // 이메일 변경 주기 확인
        var accountConfig = await AccountConfigLoader.GetAccountConfigAsync(cache, db, ct);
        if (accountConfig.ChangeEmailDay is > 0 && member.LastEmailChangedAt.HasValue)
        {
            var nextChangeDate = member.LastEmailChangedAt.Value.AddDays(accountConfig.ChangeEmailDay.Value);
            if (DateTime.UtcNow < nextChangeDate)
            {
                return Result.Failure(Error.Problem("MyPage.EmailChangeTooSoon",
                    $"이메일은 {accountConfig.ChangeEmailDay}일마다 변경 가능합니다."));
            }
        }

        if (member.Email == newEmail)
        {
            return Result.Failure(Error.Problem("MyPage.SameEmail", "현재 이메일과 동일합니다."));
        }

        var emailExists = await db.Member.AnyAsync(m => m.Email == newEmail, ct);
        if (emailExists)
        {
            return Result.Failure(Error.Conflict("MyPage.EmailExists", "이미 사용 중인 이메일입니다."));
        }

        // 인증 토큰 생성
        var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
        var expiration = DateTime.UtcNow.AddHours(24);

        var verifyToken = EmailVerifyToken.Create(
            VerificationType.ChangedEmail,
            newEmail,
            token,
            expiration,
            new AdditionalData { Email = member.Email }
        );

        await db.EmailVerifyToken.AddAsync(verifyToken, ct);

        // 변경 로그 기록
        var log = new MemberEmailChangeLog(
            request.MemberID,
            member.Email,
            newEmail,
            request.Referrer,
            request.IpAddress,
            request.UserAgent
        );
        await db.MemberEmailChangeLog.AddAsync(log, ct);

        await db.SaveChangesAsync(ct);

        // 인증 메일 발송
        var frontURL = options.Value.App.FrontURL.TrimEnd('/');
        var verifyUrl = $"{frontURL}/verify-email?token={Uri.EscapeDataString(token)}";

        await mailService.SendAsync(new SendData(
            newEmail,
            "[bitforum] 이메일 인증",
            $"<p>아래 링크를 클릭하여 이메일 인증을 완료해주세요.</p><p><a href=\"{verifyUrl}\">{verifyUrl}</a></p><p>이 링크는 24시간 동안 유효합니다.</p>"
        ), ct);

        return Result.Success();
    }
}
