using System;
using System.Data;
using Dapper;
using Oracle.ManagedDataAccess.Client;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using System.ServiceModel;

namespace SicboSub.StartProcedure
{
    public class RankingService
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(RankingService));
        private readonly string _connectionString;
        SicboSubWss.SicboSubWsClient wsClient;
        string wsUser;
        string wsPass;
        string wsPackage;

        public RankingService(string connectionString, IConfiguration configuration)
        {
            _connectionString = connectionString;
            wsUser = configuration["wsUser"];
            wsPass = configuration["wsPass"];
            wsPackage = configuration["SERVICE_ID"];
            wsClient = new SicboSubWss.SicboSubWsClient();
            SetWsClient(ref wsClient, configuration);
        }

        protected void SetWsClient(ref SicboSubWss.SicboSubWsClient wsClient, IConfiguration configuration)
        {
            wsClient.Endpoint.Address = new EndpointAddress(configuration.GetSection("wsUrl").Value);
        }

        public async Task ExecuteDailyRankingAsync(DateTime reportDate)
        {
            await RunRankingProcess(reportDate, "DAILY");
        }

        public async Task ExecuteMonthlyRankingAsync(DateTime reportDate)
        {
            await RunRankingProcess(reportDate, "MONTHLY");
        }

        private async Task RunRankingProcess(DateTime reportDate, string type)
        {
            string typeVN = type == "DAILY" ? "NGÀY" : "THÁNG";
            log.Info($"[BẮT ĐẦU] Tính toán BXH {typeVN} cho ngày {reportDate:yyyy-MM-dd}...");

            using (var conn = new OracleConnection(_connectionString))
            {
                await conn.OpenAsync();

                try
                {
                    // 1. KIỂM TRA ĐÃ CHẠY CHƯA (Tránh chạy lại Procedure nhiều lần nếu restart app)
                    string sqlCheck = "SELECT COUNT(1) FROM RANKING_HISTORY WHERE RANK_TYPE = :type AND TRUNC(RANK_DATE) = TRUNC(:rDate)";
                    int count = await conn.ExecuteScalarAsync<int>(sqlCheck, new { type = type, rDate = reportDate });

                    if (count > 0)
                    {
                        log.Info($" -> Đã có dữ liệu BXH {type} cho ngày {reportDate:yyyy-MM-dd}. Bỏ qua bước tính toán (Procedure).");
                    }
                    else
                    {
                        var p = new DynamicParameters();
                        p.Add("p_report_date", reportDate, DbType.Date, ParameterDirection.Input);
                        p.Add("p_type", type, DbType.String, ParameterDirection.Input);

                        await conn.ExecuteAsync("PRC_CALCULATE_RANKING", p, commandType: CommandType.StoredProcedure);
                        log.Info(" -> Tính toán BXH (Procedure) hoàn tất.");
                    }

                    // 2. QUÉT ALL PENDING (Kể cả những cái bị treo từ lần chạy cũ bị crash)
                    // Không chỉ lấy của ngày hôm nay mà lấy tất cả status=0 để xử lý triệt để
                    // Join voi RANKING_HISTORY de lay thong tin RankType va Position
                    string sqlGetPending = @"
                        SELECT t1.ID, t1.MSISDN, t1.REWARD_VALUE, t1.REWARD_UNIT, t1.NOTE,
                               t2.RANK_TYPE, t2.RANK_POSITION
                        FROM RANKING_REWARD_LOG t1
                        JOIN RANKING_HISTORY t2 ON t1.RANKING_HISTORY_ID = t2.ID
                        WHERE t1.STATUS = 0
                        ORDER BY t1.ID ASC"; // FIFO

                    var pendingRewards = await conn.QueryAsync<RankingRewardLog>(sqlGetPending);
                    var rewardList = pendingRewards.ToList();
                    
                    if (rewardList.Count == 0)
                    {
                        log.Info(" -> Không có phần thưởng nào cần xử lý.");
                        return;
                    }

                    log.Info($" -> Tìm thấy {rewardList.Count} lệnh thưởng đang chờ (Status=0).");

                    // 3. XỬ LÝ AN TOÀN (Claim -> Process -> Finish)
                    foreach (var reward in rewardList)
                    {
                        await ProcessSingleRewardSafe(conn, reward);
                    }
                }
                catch (Exception ex)
                {
                    log.Error($"[LỖI NGHIÊM TRỌNG] {ex.Message}");
                }
            }
            log.Info("[HOÀN TẤT] Quá trình kết thúc.\n");
        }

        /// <summary>
        /// Xử lý từng giao dịch với cơ chế 'Chiếm quyền' (Optimistic Locking) tránh tranh chấp.
        /// </summary>
        private async Task ProcessSingleRewardSafe(OracleConnection conn, RankingRewardLog reward)
        {
            // BƯỚC 1: CHIẾM QUYỀN (Mark as Processing - 3)
            // Chỉ update được nếu Status vẫn đang là 0.
            string sqlClaim = "UPDATE RANKING_REWARD_LOG SET STATUS = 3 WHERE ID = :id AND STATUS = 0";
            int rowsAffected = await conn.ExecuteAsync(sqlClaim, new { id = reward.ID });

            if (rowsAffected == 0)
            {
                // Ai đó đã xử lý rồi, hoặc trạng thái đã thay đổi -> Bỏ qua
                log.Warn($"    ! Bỏ qua ID {reward.ID} (User {reward.MSISDN}) - Đã bị chiếm bởi tiến trình khác.");
                return;
            }

            // CHECK YÊU CẦU ĐẶC BIỆT: Top 1 Monthly -> Không trả tự động, chuyển Status 4 (Thủ công)
            if (reward.RANK_TYPE == "MONTHLY" && reward.RANK_POSITION == 1 && reward.REWARD_UNIT == "MONEY")
            {
                 log.Info($"    ! Monthly Top 1 Prize (User {reward.MSISDN}) - Bỏ qua Add Money, chuyển sang trạng thái 4 (Thủ công).");
                 await conn.ExecuteAsync("UPDATE RANKING_REWARD_LOG SET STATUS = 4, CREATED_DATE = SYSDATE WHERE ID = :id", new { id = reward.ID });
                 return;
            }

            // BƯỚC 2: THỰC HIỆN TRẢ THƯỞNG (Payment Logic)
            log.Info($"    -> Đang xử lý ID {reward.ID} (User {reward.MSISDN}): {reward.REWARD_VALUE} {reward.REWARD_UNIT}...");
            bool isSuccess = await DistributeReward(conn, reward);

            // BƯỚC 3: CHỐT TRẠNG THÁI (1: Success, 2: Failed)
            // Theo yêu cầu: Trả thưởng thất bại sẽ set về 2 (Failed) để Admin kiểm tra thủ công.
            // Hệ thống KHÔNG tự động retry các giao dịch lỗi này.
            int finalStatus = isSuccess ? 1 : 2; 

            string sqlFinalize = "UPDATE RANKING_REWARD_LOG SET STATUS = :s, CREATED_DATE = SYSDATE WHERE ID = :id";
            await conn.ExecuteAsync(sqlFinalize, new { s = finalStatus, id = reward.ID });

            log.Info($"       -> KẾT QUẢ: {(isSuccess ? "THÀNH CÔNG" : "THẤT BẠI")}");
        }

        private async Task<bool> DistributeReward(OracleConnection conn, RankingRewardLog reward)
        {
            try
            {
                // Giả lập độ trễ mạng
                await Task.Delay(50); 
                
                if (reward.REWARD_UNIT == "COIN")
                {
                    // Call Service Add Coin
                    return await AddCoin(conn, reward.MSISDN, reward.REWARD_VALUE);
                }
                else if (reward.REWARD_UNIT == "MONEY")
                {
                    // Call Service Add Money (WS)
                    if (reward.REWARD_VALUE <= 0) return true;

                    SicboSubWss.wsAddMoneyResponse response = await wsClient.wsAddMoneyAsync(
                        wsUser,
                        wsPass,
                        reward.MSISDN,
                        wsPackage,
                        reward.REWARD_VALUE.ToString(),
                        wsPackage,
                        reward.RANK_POSITION.ToString(),
                        reward.RANK_TYPE
                    );

                     if (response != null && response.@return != null && response.@return.errorCode == "0")
                    {
                        return true;
                    }
                    log.Error($"       [API ERROR] ErrorCode: {response?.@return?.errorCode}, Message: {response?.@return?.content}");
                    return false;
                }

                return true; 
            }
            catch (Exception ex)
            {
                log.Error($"       [LỖI API] {ex.Message}");
                return false;
            }
        }

        private async Task<bool> AddCoin(OracleConnection conn, string msisdn, decimal amount)
        {
            try 
            {
                string sql = "UPDATE ACCOUNT_USER SET WIN_COIN = WIN_COIN + :amount WHERE MSISDN = :msisdn";
                int rows = await conn.ExecuteAsync(sql, new { amount = amount, msisdn = msisdn });
                return rows > 0;
            }
            catch(Exception ex)
            {
                log.Error($"       [DB ERROR] AddCoin: {ex.Message}");
                return false;
            }
        }
    }

    public class RankingRewardLog
    {
        public decimal ID { get; set; }
        public string MSISDN { get; set; } = "";
        public decimal REWARD_VALUE { get; set; }
        public string REWARD_UNIT { get; set; } = "";
        public string NOTE { get; set; } = "";
        public string RANK_TYPE { get; set; } = "";
        public decimal RANK_POSITION { get; set; }
    }
}
