using LSFE.Domain.Contracts.Planning;
using LSFE.Infrastructure.Database;
using LSFE.Infrastructure.Dto;
using LSFE.Infrastructure.Dto.Doctor;
using LSFE.Infrastructure.Dto.Planning;
using LSFE.Infrastructure.Entities.Maintenance;
using LSFE.Infrastructure.Entities.Planning;
using LSFE.Infrastructure.Model;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualBasic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using static LSFE.Domain.Services.OutputParamMessage;

namespace LSFE.Domain.Services.Planning
{
    public class PlanningRepo : GenericRepository<MRRawPlan>, IPlanningRepo
    {
        private readonly LSFEDbContext _context;
        public PlanningRepo(LSFEDbContext context) : base(context)
        {
            _context = context;
        }
        #region Post Put
        public async Task<Response> SoftDeleteAsync(int doctorId)
        {
            var response = new Response();

            try
            {
                var existingDoctor = await GetByIdAsync(doctorId);

                if (existingDoctor == null)
                {
                    response.Success = false;
                    response.Message = $"Doctor with ID {doctorId} not found.";
                    response.MessCode = 0;
                    return response;
                }

                Update(existingDoctor);
                await _context.SaveChangesAsync();

                response.Success = true;
                response.Data = existingDoctor;
                response.Message = "Doctor deactivated successfully.";
                response.MessCode = 3; // code for delete/soft delete
            }
            catch (Exception ex)
            {
                response.Success = false;
                response.Message = ex.Message;
                response.MessCode = 0;
            }

            return response;
        }
        public async Task<Response> PostPutPlan(PlanningDto dto)
        {
            // Input validation
            if (dto == null)
                return new Response { Success = false, Message = "Invalid request data", MessCode = 0 };

            var (messCode, message, planId) = CreateOutputParamsPlanId();

            using var transaction = await _context.Database.BeginTransactionAsync();

            try
            {
                // Execute the stored procedure
                await _context.Database.ExecuteSqlRawAsync(
                    "EXEC PostPutPlan @UserId,@DoctorId,@CallDate,@CallType,@SLP," +
                    "@IsJoinCall,@IsLiterature,@MRPlanDetailId,@MRRawPlanId,@IsUpdateDelete,@MessCode OUTPUT,@Message OUTPUT,@MRRawPlanIdOutPut OUTPUT",
                    new SqlParameter("@UserId", dto.UserId),
                    new SqlParameter("@DoctorId", dto.DoctorId),
                    new SqlParameter("@CallDate", dto.CallDate),
                    new SqlParameter("@CallType", dto.SLP ?? "N/A"),
                    new SqlParameter("@SLP", dto.SLP ?? "N/A"),
                    new SqlParameter("@IsJoinCall", false), 
                    new SqlParameter("@IsLiterature", dto.IsLiterature),
                    new SqlParameter("@MRPlanDetailId",
                        string.IsNullOrEmpty(dto.MRPlanDetailId) || dto.MRPlanDetailId == "N/A"
                            ? (object)DBNull.Value
                            : Guid.Parse(dto.MRPlanDetailId)),
                    new SqlParameter("@MRRawPlanId",
                        string.IsNullOrEmpty(dto.MRRawPlanId) || dto.MRRawPlanId == "N/A"
                            ? (object)DBNull.Value
                            : Guid.Parse(dto.MRRawPlanId)),
                    new SqlParameter("@IsUpdateDelete", dto.IsUpdateDelete),
                    messCode,
                    message,
                    planId);

                var code = (byte)(messCode.Value ?? 0);
                var msg = message.Value?.ToString() ?? "No response message.";
                var mrRawPlanId = planId.Value?.ToString() ?? string.Empty;
                var success = code != 0;

                // If stored procedure failed, rollback and return
                if (!success)
                {
                    await transaction.RollbackAsync();
                    return new Response { Success = false, Message = msg, MessCode = code };
                }

                // Validate that we have a valid plan ID for insert/update operations
                if (string.IsNullOrEmpty(mrRawPlanId) && dto.IsUpdateDelete != 1)
                {
                    await transaction.RollbackAsync();
                    return new Response { Success = false, Message = "You cannot create more than one visit in a week!", MessCode = 0 };
                }

                // Get username for audit trail
                var username = await _context.Users
                    .Where(u => u.Id == dto.UserId)
                    .Select(u => u.UserName)
                    .FirstOrDefaultAsync();

                if (string.IsNullOrEmpty(username))
                {
                    await transaction.RollbackAsync();
                    return new Response { Success = false, Message = "User not found", MessCode = 0 };
                }

                var now = DateTime.Now;
                DateOnly today = DateOnly.FromDateTime(DateTime.Now);

                // Handle inventory based on operation type
                if (dto.IsUpdateDelete == 1 || dto.IsUpdateDelete == 2) // UPDATE/DELETE
                {

                    if (!IsSameMonth(dto.CallDate, today))
                    {
                        msg = await HandleInventoryDelete(dto, msg);
                        msg = await HandlePromosDelete(dto, msg);

                        msg = await HandleInventoryInsert(dto, msg, username, now, mrRawPlanId);
                        msg = await HandlePromosInsert(dto, msg, username, now, mrRawPlanId);
                    }
                }
                else if (dto.IsUpdateDelete == 2) // UPDATE
                {               
                    if (!IsSameMonth(dto.CallDate, today))
                    {
                        msg = await HandleInventoryUpdate(dto, msg, username, now);
                        msg = await HandlePromosUpdate(dto, msg, username, now, mrRawPlanId);
                    }
                }
                else // INSERT (new plan)
                {
                    msg = await HandleInventoryInsert(dto, msg, username, now, mrRawPlanId);
                    msg = await HandlePromosInsert(dto, msg, username, now, mrRawPlanId);
                }

                await _context.SaveChangesAsync();
                await transaction.CommitAsync();

                return new Response
                {
                    Success = true,
                    Message = msg,
                    MessCode = code
                };
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                return new Response
                {
                    Success = false,
                    Message = $"Error processing plan: {ex.Message}",
                    MessCode = 0
                };
            }
        }
        private bool IsSameMonth(DateOnly date, DateOnly reference)
        {
            return date.Year == reference.Year && date.Month == reference.Month;
        }

        private async Task<string> HandleInventoryDelete(PlanningDto dto, string msg)
        {
            if (string.IsNullOrEmpty(dto.MRRawPlanId))
                return msg + " Cannot delete inventory: Missing MRRawPlanId.";

            var mrRawPlanGuid = Guid.Parse(dto.MRRawPlanId);

            // Get all inventory transactions (ONE query only)
            var transactions = await _context.InventoryTransactions
                .Where(t => t.MRRawPlanId == mrRawPlanGuid)
                .ToListAsync();

            if (transactions.Count == 0)
                return msg + " No linked inventory transactions found.";

            // Collect inventory IDs for batch query
            var inventoryIds = transactions
                .Select(t => t.InventoryId)
                .Distinct()
                .ToList();

            // Load inventories in ONE query
            var inventories = await _context.Inventory
                .Where(i => inventoryIds.Contains(i.InventoryId))
                .ToDictionaryAsync(i => i.InventoryId);

            // Process each transaction update
            foreach (var tx in transactions)
            {
                if (!inventories.TryGetValue(tx.InventoryId, out var inventory))
                    continue; // if missing, skip safely

                inventory.QtyOut -= tx.QtyIn;        // revert qty out
                inventory.QtyBalance += tx.QtyIn;    // restore qty

                if (inventory.QtyOut < 0)
                    inventory.QtyOut = 0; // safety check

                _context.Inventory.Update(inventory);
            }

            // Remove all transaction records
            _context.InventoryTransactions.RemoveRange(transactions);

            return msg + $" Deleted {transactions.Count} record(s).";
        }

        private async Task<string> HandleInventoryUpdate(PlanningDto dto, string msg,
            string username, DateTime now)
        {
            if (dto.SelectedSamples == null || !dto.SelectedSamples.Any())
                return msg;

            if (string.IsNullOrEmpty(dto.MRRawPlanId))
            {
                return msg + " Cannot update inventory: Missing MRRawPlanId.";
            }

            var mrRawPlanGuid = Guid.Parse(dto.MRRawPlanId);

            foreach (var sample in dto.SelectedSamples)
            {
                var inventoryTransaction = await _context.InventoryTransactions
                    .FirstOrDefaultAsync(i => i.InventoryId == sample.InventoryId
                        && i.MRRawPlanId == mrRawPlanGuid);

                var inventory = await _context.Inventory
                    .FirstOrDefaultAsync(i => i.InventoryId == sample.InventoryId);

                if (inventory == null)
                {
                    msg += $" {sample.InventoryId}: Inventory not found.";
                    continue;
                }

                if (inventoryTransaction == null)
                {
                    msg += $" {inventory.Description}: No existing transaction found.";
                    continue;
                }

                // Calculate quantity difference
                int newQtyBalance = inventory.QtyIn - sample.SelectedQty;

                //Update the new qty balance
                inventory.QtyBalance = newQtyBalance;

                // Validate balance
                if (inventory.QtyBalance < 0)
                {
                    msg += $" {inventory.Description}: Insufficient stock for update.";
                    continue;
                }

                if (inventory.QtyBalance > inventory.QtyIn)
                {
                    msg += $" {inventory.Description}: Balance exceeds original quantity.";
                    continue;
                }

                // Update transaction
                inventoryTransaction.AppointmentDate = dto.CallDate;
                inventoryTransaction.QtyOut = sample.SelectedQty;
                inventoryTransaction.QtyIn = sample.SelectedQty;
                inventoryTransaction.UpdatedBy = username;
                inventoryTransaction.UpdatedDate = now;

                _context.InventoryTransactions.Update(inventoryTransaction);
                _context.Inventory.Update(inventory);

                msg += $" {inventory.Description}: Updated from {sample.OriginalQty} to {sample.SelectedQty} units.";
            }

            return msg;
        }

        private async Task<string> HandleInventoryInsert(PlanningDto dto, string msg,
            string username, DateTime now, string mrRawPlanId)
        {
            if (dto.SelectedSamples == null || !dto.SelectedSamples.Any())
                return msg;

            if (!Guid.TryParse(mrRawPlanId, out var planGuid))
            {
                return msg + " Invalid plan ID for inventory insert.";
            }

            var inventoryWarnings = new List<string>();

            foreach (var sample in dto.SelectedSamples)
            {
                var inventory = await _context.Inventory
                    .FirstOrDefaultAsync(i => i.InventoryId == sample.InventoryId);

                if (inventory == null)
                {
                    inventoryWarnings.Add($"{sample.InventoryId}: Not found");
                    continue;
                }

                // Calculate quantity difference
                int newQtyBalance = inventory.QtyIn - sample.SelectedQty;


                // Create transaction
                var inventoryTransaction = new InventoryTransaction
                {
                    InventoryId = sample.InventoryId,
                    DoctorId = dto.DoctorId,
                    Description = sample.Description ?? inventory.Description ?? "Sample",
                    MRRawPlanId = planGuid,
                    AppointmentDate = dto.CallDate,
                    CreatedDate = now,
                    QtyIn = sample.SelectedQty,
                    QtyOut = sample.SelectedQty,
                    UserId = dto.UserId,
                    CreatedBy = username,
                    IsActive = true
                };
                _context.InventoryTransactions.Add(inventoryTransaction);

                // Deduct from inventory
                inventory.QtyBalance = newQtyBalance;
                inventory.QtyOut = sample.SelectedQty;
                inventory.UpdatedBy = username;
                inventory.UpdatedDate = now;
                _context.Inventory.Update(inventory);
            }

            if (inventoryWarnings.Any())
            {
                msg += $" (Inventory warnings: {string.Join("; ", inventoryWarnings)})";
            }

            return msg;
        }

        private async Task<string> HandlePromosDelete(PlanningDto dto, string msg)
        {
            if (dto.SelectedPromos == null || !dto.SelectedPromos.Any())
                return msg;

            if (string.IsNullOrEmpty(dto.MRRawPlanId))
            {
                return msg + " Cannot delete promos: Missing MRRawPlanId.";
            }

            var mrRawPlanGuid = Guid.Parse(dto.MRRawPlanId);

            var promosToDelete = await _context.ProductTransactions
                .Where(pt => pt.MRRawPlanId == mrRawPlanGuid)
                .ToListAsync();

            _context.ProductTransactions.RemoveRange(promosToDelete);

            return msg + $" Deleted {promosToDelete.Count} promo(s).";
        }

        private async Task<string> HandlePromosUpdate(PlanningDto dto, string msg,
            string username, DateTime now, string mrRawPlanId)
        {
            if (dto.SelectedPromos == null || !dto.SelectedPromos.Any())
                return msg;

            if (string.IsNullOrEmpty(dto.MRRawPlanId))
            {
                return msg + " Cannot update promos: Missing MRRawPlanId.";
            }

            var mrRawPlanGuid = Guid.Parse(dto.MRRawPlanId);

            // Delete existing promos
            var existingPromos = await _context.ProductTransactions
                .Where(pt => pt.MRRawPlanId == mrRawPlanGuid)
                .ToListAsync();

            _context.ProductTransactions.RemoveRange(existingPromos);

            // Insert new promos
            foreach (var product in dto.SelectedPromos)
            {
                var productTransaction = new ProductTransaction
                {
                    ProductId = product.ProductId,
                    DoctorId = dto.DoctorId,
                    ProductName = product.ProductName,
                    AppointmentDate = dto.CallDate,
                    MRRawPlanId = mrRawPlanGuid,
                    CreatedDate = now,
                    CreatedBy = username,
                    UserId = dto.UserId,
                    IsActive = true
                };

                _context.ProductTransactions.Add(productTransaction);
            }

            return msg + $" Updated promos: removed {existingPromos.Count}, added {dto.SelectedPromos.Count}.";
        }

        private async Task<string> HandlePromosInsert(PlanningDto dto, string msg,
            string username, DateTime now, string mrRawPlanId)
        {
            if (dto.SelectedPromos == null || !dto.SelectedPromos.Any())
                return msg;

            if (!Guid.TryParse(mrRawPlanId, out var planGuid))
            {
                return msg + " Invalid plan ID for promo insert.";
            }

            foreach (var product in dto.SelectedPromos)
            {
                var productTransaction = new ProductTransaction
                {
                    ProductId = product.ProductId,
                    DoctorId = dto.DoctorId,
                    ProductName= product.ProductName,
                    AppointmentDate = dto.CallDate,
                    MRRawPlanId = planGuid,
                    CreatedDate = now,
                    CreatedBy = username,
                    UserId = dto.UserId,
                    IsActive = true
                };

                _context.ProductTransactions.Add(productTransaction);
            }

            return msg;
        }
        public async Task<Response> ApprovePlan(ApprovePlanDto dto)
        {
            if (dto == null || dto.AppointmentIds == null || !dto.AppointmentIds.Any())
                return new Response { Success = false, Message = "No appointments selected", MessCode = 0 };

            // Filter out empty GUIDs
            var guidList = dto.AppointmentIds.Where(id => id != Guid.Empty).ToList();

            if (!guidList.Any())
                return new Response { Success = false, Message = "No valid appointment ids", MessCode = 0 };

            using var tx = await _context.Database.BeginTransactionAsync();
            try
            {
                var plans = await _context.MRRawPlans
                    .Where(p => guidList.Contains(p.MRRawPlanId))
                    .ToListAsync();

                if (!plans.Any())
                    return new Response { Success = false, Message = "No matching appointments found", MessCode = 0 };

                int approvedCount = 0;
                int alreadyApproved = 0;
                var now = DateTime.UtcNow;
                var userIdOrSystem = dto.UpdatedBy ?? "System";

                foreach (var plan in plans)
                {
                    if (plan.IsApproved == true)
                    {
                        alreadyApproved++;
                        continue;
                    }
                    plan.IsApproved = true;
                    plan.ApprovedBy = userIdOrSystem;
                    plan.ApprovedDate = now;
                    approvedCount++;
                }

                if (approvedCount > 0)
                {
                    _context.MRRawPlans.UpdateRange(plans.Where(p => p.IsApproved == true));
                    await _context.SaveChangesAsync();
                }

                await tx.CommitAsync();

                return new Response
                {
                    Success = true,
                    Message = $"{approvedCount} approved, {alreadyApproved} already approved.",
                    MessCode = 1
                };
            }
            catch (Exception ex)
            {
                await tx.RollbackAsync();
                return new Response { Success = false, Message = $"Error: {ex.Message}", MessCode = 0 };
            }
        }

        public async Task<Response> CopyMonthlyMRRawPlans(PlanningDto dto)
        {
            try
            {
                var (messCode, message) = CreateOutputParams();

                // Execute the stored procedure
                await _context.Database.ExecuteSqlRawAsync(
                    "EXEC CopyMonthlyMRRawPlans @MonthPlanCopy,@NewMonthPlan,@UserId,@MessCode OUTPUT,@Message OUTPUT",
                    new SqlParameter("@MonthPlanCopy", dto.MonthPlanCopy),
                    new SqlParameter("@NewMonthPlan", dto.NewMonthPlan),
                    new SqlParameter("@UserId", dto.UserId),
                    messCode,
                    message);

                return new Response
                {
                    Success = true,
                    Message = message.Value.ToString(),
                    MessCode = (byte)(messCode.Value ?? 0)
                };
            }
            catch (Exception ex)
            {

                return new Response
                {
                    Success = true,
                    Message = ex.ToString() ?? ex.InnerException.ToString(),
                    MessCode = 0
                };
            }

        }
        #endregion

        #region Get
        public async Task<List<DoctorWithPlan>> GetDoctorWithPlan(DoctorDto dto)
        {
            string spName = dto.IsPlanning
                ? "GetDoctorWithPlan"
                : "GetDoctorWithPlanStatus";

            var allItems = await _context.DoctorWithPlan
                .FromSqlRaw(
                    $"EXEC {spName} @UserId, @MRPlanDate",
                    new SqlParameter("@UserId", dto.UserId),
                    new SqlParameter("@MRPlanDate", dto.MRPlanDate))
                .ToListAsync();

            return allItems ?? new List<DoctorWithPlan>();
        }

        public async Task<List<MRRawPlan>> GetPlan(PlanningDto dto)
        {
            var allItems = await _context.MRRawPlans
                    .FromSqlRaw("EXEC GetPlan @UserId",
                    new SqlParameter("@UserId", dto.UserId))
                    .ToListAsync();

            return allItems ?? new List<MRRawPlan>();
        }
        public async Task<List<DoctorById>> GetDoctorById(DoctorDto dto)
        {
            var allItems = await _context.DoctorByIds
                   .FromSqlRaw("EXEC GetDoctorById @UserId,@DoctorId,@MRPlanDate",
                   new SqlParameter("@UserId", dto.UserId),
                   new SqlParameter("@DoctorId", dto.DoctorId),
                   new SqlParameter("@MRPlanDate", dto.MRPlanDate))
                   .ToListAsync();

            return allItems ?? new List<DoctorById>();
        }
        public async Task<List<PendingApproval>> GetPendingApprovals(PlanningDto dto)
        {
            var mrPlanDate = GetValidDate(dto.MRPlanDate);

            var allItems = await _context.PendingApprovals
                .FromSqlRaw(
                    "EXEC GetPendingApprovals @UserId,@DoctorId,@MRPlanDate",
                    new SqlParameter("@UserId", dto.UserId),
                    new SqlParameter("@DoctorId", dto.DoctorId),
                    new SqlParameter("@MRPlanDate", mrPlanDate)
                )
                .ToListAsync();

            return allItems ?? new List<PendingApproval>();
        }
        private static DateTime GetValidDate(object? input)
        {
            if (input is DateTime dt)
                return dt;

            if (input is string s && DateTime.TryParse(s, out var parsed))
                return parsed;

            return DateTime.UtcNow;
        }
        
        public async Task<List<MRRawPlan>> GetAllAppointmentToday(CredentialDto dto)
        {
            var allItems = await _context.MRRawPlans
             .FromSqlRaw("EXEC GetAllAppointmentToday @UserId",
             new SqlParameter("@UserId", dto.UserId))
             .ToListAsync();

            return allItems ?? new List<MRRawPlan>();
        }
        public async Task<List<MRRawPlan>> GetAllMissReschedule(CredentialDto dto)
        {
            var allItems = await _context.MRRawPlans
             .FromSqlRaw("EXEC GetAllMissReschedule @UserId,@IsMissed,@IsReschedule",
             new SqlParameter("@UserId", dto.UserId),
             new SqlParameter("@IsMissed", dto.IsMissed),
             new SqlParameter("@IsReschedule", dto.IsReschedule))
             .ToListAsync();

            return allItems ?? new List<MRRawPlan>();
        }

        public async Task<List<InventoryTransaction>> GetSampleTransaction(Guid mrRawPlanId)
        {
            var allItems = await _context.InventoryTransactions
                  .FromSqlRaw("EXEC GetSampleTransaction @MRRawPlanId",
                  new SqlParameter("@MRRawPlanId", mrRawPlanId)).ToListAsync();

            return allItems ?? new List<InventoryTransaction>();
        }

        public async Task<List<ProductTransaction>> GetProductTransaction(Guid mrRawPlanId)
        {
            var allItems = await _context.ProductTransactions
                  .FromSqlRaw("EXEC GetProductTransaction @MRRawPlanId",
                new SqlParameter("@MRRawPlanId", mrRawPlanId)).ToListAsync();

            return allItems ?? new List<ProductTransaction>();
        }

        public async Task<List<MRRawPlansDto>> GetMRRawPlans(PlanByUserIdRequest dto)
        {
            var plans = await _context.MRRawPlansDto
                           .FromSqlRaw("EXEC GetMRRawPlans @UserId",
                         new SqlParameter("@UserId", dto.UserId)).ToListAsync();

            return plans ?? new List<MRRawPlansDto>();
        }
        #endregion
    }
}
