using System.Text.Json;
using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using SharedKernel.Results;
using Domain.Entities.Common.ValueObject;
using Domain.Entities.Members.Logs;
using Microsoft.EntityFrameworkCore;

namespace Application.Features.Api.Attendance.CheckIn;

internal sealed class Handler(IAppDbContext db) : ICommandHandler<Command, Result<Response>>
{
    private static readonly JsonSerializerOptions _rankBonusJsonOptions = new() { PropertyNameCaseInsensitive = true };

    public async Task<Result<Response>> Handle(Command r, CancellationToken ct)
    {
        if (r.MemberID <= 0)
        {
            return Result.Failure<Response>(Error.Unauthorized("Attendance.LoginRequired", "로그인이 필요합니다."));
        }

        // 출석 설정 조회 (db.Config 직접 읽기 — Config.Get/Response 는 편집 대상 아님)
        var config = await db.Config.AsNoTracking().OrderByDescending(x => x.ID).FirstOrDefaultAsync(ct);
        if (config is null || !config.Attendance.IsEnabled)
        {
            return Result.Failure<Response>(Error.Problem("Attendance.Disabled", "출석 기능이 비활성화 상태입니다."));
        }

        var attendanceConfig = config.Attendance;

        // 로컬(서버 KST) 기준 "오늘"을 UTC 범위로 환산
        var todayStartUtc = DateTime.Now.Date.ToUniversalTime();
        var tomorrowStartUtc = todayStartUtc.AddDays(1);
        var yesterdayStartUtc = todayStartUtc.AddDays(-1);

        // 오늘 이미 출석했는지 체크
        var alreadyCheckedIn = await db.Attendance.AsNoTracking()
            .AnyAsync(x => x.MemberID == r.MemberID && x.CreatedAt >= todayStartUtc && x.CreatedAt < tomorrowStartUtc, ct);

        if (alreadyCheckedIn)
        {
            return Result.Failure<Response>(Error.Conflict("Attendance.AlreadyCheckedIn", "오늘은 이미 출석하셨습니다."));
        }

        // 어제 출석 기록으로 연속 출석 계산
        var yesterdayRecord = await db.Attendance.AsNoTracking()
            .Where(x => x.MemberID == r.MemberID && x.CreatedAt >= yesterdayStartUtc && x.CreatedAt < todayStartUtc)
            .Select(x => new { x.ConsecutiveDays })
            .FirstOrDefaultAsync(ct);

        var consecutiveDays = yesterdayRecord is not null ? yesterdayRecord.ConsecutiveDays + 1 : 1;

        // 보상 계산
        var baseExp = attendanceConfig.BaseExp;
        var basePoint = attendanceConfig.BasePoint;
        var bonusExp = 0;
        var bonusPoint = 0;

        if (attendanceConfig.UseStreakBonus && consecutiveDays > 1)
        {
            var streakDays = Math.Min(consecutiveDays - 1, attendanceConfig.StreakBonusMaxDays);
            bonusExp = attendanceConfig.StreakBonusPerDay * streakDays;
            bonusPoint = attendanceConfig.StreakBonusPointPerDay * streakDays;
        }

        var totalExp = baseExp + bonusExp;
        var totalPoint = basePoint + bonusPoint;

        var greeting = string.IsNullOrWhiteSpace(r.Greeting) ? "" : r.Greeting.Trim();
        if (greeting.Length > 200)
        {
            greeting = greeting[..200];
        }

        // 출석 기록 생성
        var attendance = Domain.Entities.Members.Attendance.Create(r.MemberID, greeting, consecutiveDays, totalExp, totalPoint);
        db.Attendance.Add(attendance);

        // MemberStats 경험치 + 출석 횟수
        var stats = await db.MemberStats.FirstOrDefaultAsync(x => x.MemberID == r.MemberID, ct);
        if (stats is not null)
        {
            stats.Exp += totalExp;
            stats.AttendanceCount++;

            // 경험치 로그
            if (totalExp > 0)
            {
                var expLog = MemberExpLog.Create(r.MemberID, "출석", totalExp, stats.Exp);
                db.MemberExpLog.Add(expLog);
            }
        }

        // 포인트 보상 (Wallet Reward)
        if (totalPoint > 0)
        {
            var wallet = await db.Wallet
                .Include(w => w.Balances)
                .FirstOrDefaultAsync(w => w.MemberID == r.MemberID, ct);

            if (wallet is not null)
            {
                wallet.CreditReward(Money.KRW(totalPoint), "ATTENDANCE", attendance.ID.ToString());
            }
        }

        await db.SaveChangesAsync(ct);

        // 오늘 출석 순위 계산
        var rank = await db.Attendance.AsNoTracking()
            .CountAsync(x => x.CreatedAt >= todayStartUtc && x.CreatedAt <= attendance.CreatedAt, ct);

        // 순위 보상 계산
        var rankBonusExp = 0;
        var rankBonusPoint = 0;

        if (attendanceConfig.UseRankBonus && rank is >= 1 and <= 10 && !string.IsNullOrWhiteSpace(attendanceConfig.RankBonusConfig))
        {
            try
            {
                var rankBonuses = JsonSerializer.Deserialize<List<RankBonusEntry>>(attendanceConfig.RankBonusConfig, _rankBonusJsonOptions);
                var entry = rankBonuses?.Find(x => x.Rank == rank);
                if (entry is not null)
                {
                    rankBonusExp = entry.Exp;
                    rankBonusPoint = entry.Point;

                    attendance.AddRankBonus(rankBonusExp, rankBonusPoint);

                    if (stats is not null && rankBonusExp > 0)
                    {
                        stats.Exp += rankBonusExp;
                        var expLog = MemberExpLog.Create(r.MemberID, "출석 순위 보상", rankBonusExp, stats.Exp);
                        db.MemberExpLog.Add(expLog);
                    }

                    if (rankBonusPoint > 0)
                    {
                        var wallet = await db.Wallet
                            .Include(w => w.Balances)
                            .FirstOrDefaultAsync(w => w.MemberID == r.MemberID, ct);

                        if (wallet is not null)
                        {
                            wallet.CreditReward(Money.KRW(rankBonusPoint), "ATTENDANCE_RANK", attendance.ID.ToString());
                        }
                    }

                    await db.SaveChangesAsync(ct);
                }
            }
            catch (JsonException)
            {
                // JSON 파싱 실패 시 순위 보상 무시
            }
        }

        return Result.Success(new Response(attendance.ID, rank, consecutiveDays, totalExp + rankBonusExp, totalPoint + rankBonusPoint, rankBonusExp, rankBonusPoint));
    }
}

internal sealed class RankBonusEntry
{
    public int Rank { get; set; }
    public int Exp { get; set; }
    public int Point { get; set; }
}
