-- =============================================
-- Stored Procedure: sp_CalculateWeeklyBalances
-- Description: محاسبه تعادل‌های هفتگی شبکه باینری با استفاده از CTE
-- Author: System
-- Created: 2025-12-19
-- =============================================

CREATE OR ALTER PROCEDURE [CMS].[sp_CalculateWeeklyBalances]
    @WeekDefinitionId BIGINT,
    @ForceRecalculate BIT = 0,
    @RowCount INT OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    DECLARE @ErrorMessage NVARCHAR(4000);
    DECLARE @StartDate DATETIME;
    DECLARE @EndDate DATETIME;
    DECLARE @PreviousWeekDefinitionId BIGINT;
    DECLARE @MaxBalancesPerLeg INT = 300;
    DECLARE @MaxNetworkLevel INT = 15;
    DECLARE @CalculatedAt DATETIME = GETDATE();

    BEGIN TRY
        BEGIN TRANSACTION;

        -- =============================================
        -- 1. اعتبارسنجی WeekDefinitionId
        -- =============================================
        IF NOT EXISTS (SELECT 1 FROM CMS.WeekDefinitions WHERE Id = @WeekDefinitionId)
        BEGIN
            SET @ErrorMessage = N'هفته ' + CAST(@WeekDefinitionId AS NVARCHAR(20)) + N' در سیستم تعریف نشده است';
            RAISERROR(@ErrorMessage, 16, 1);
            RETURN;
        END

        -- دریافت بازه تاریخی هفته
        SELECT @StartDate = StartDate, @EndDate = EndDate
        FROM CMS.WeekDefinitions
        WHERE Id = @WeekDefinitionId;

        -- =============================================
        -- 2. بررسی محاسبه قبلی
        -- =============================================
        IF EXISTS (SELECT 1 FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId)
        BEGIN
            IF @ForceRecalculate = 0
            BEGIN
                SET @ErrorMessage = N'تعادل‌های هفته ' + CAST(@WeekDefinitionId AS NVARCHAR(20)) + N' قبلاً محاسبه شده است. برای محاسبه مجدد از ForceRecalculate استفاده کنید';
                RAISERROR(@ErrorMessage, 16, 1);
                RETURN;
            END
            
            -- حذف محاسبات قبلی
            DELETE FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId;
        END

        -- =============================================
        -- 3. پیدا کردن هفته قبل
        -- =============================================
        SELECT TOP 1 @PreviousWeekDefinitionId = Id
        FROM CMS.WeekDefinitions
        WHERE WeekOrder = (SELECT WeekOrder - 1 FROM CMS.WeekDefinitions WHERE Id = @WeekDefinitionId)
        AND IsActive = 1;

        -- =============================================
        -- 4. مقادیر ثابت (Hardcoded - از SystemConstants)
        -- =============================================
        -- این مقادیر ثابت هستند و تغییر نمی‌کنند
        SET @MaxBalancesPerLeg = 300;  -- سقف تعادل هر پا
        SET @MaxNetworkLevel = 15;     -- حداکثر عمق شبکه

        -- =============================================
        -- 5. ایجاد جدول موقت برای نتایج
        -- =============================================
        CREATE TABLE #Balances (
            UserId BIGINT PRIMARY KEY,
            LeftLegNewMembers INT DEFAULT 0,
            RightLegNewMembers INT DEFAULT 0,
            LeftLegCarryover INT DEFAULT 0,
            RightLegCarryover INT DEFAULT 0,
            LeftLegTotal INT DEFAULT 0,
            RightLegTotal INT DEFAULT 0,
            TotalBalances INT DEFAULT 0,
            LeftLegRemainder INT DEFAULT 0,
            RightLegRemainder INT DEFAULT 0,
            FlushedPerSide INT DEFAULT 0,
            TotalFlushed INT DEFAULT 0,
            SubordinateBalances INT DEFAULT 0
        );

        -- =============================================
        -- 6. دریافت کاربران فعال باشگاه
        -- =============================================
        INSERT INTO #Balances (UserId)
        SELECT DISTINCT u.Id
        FROM CMS.Users u
        INNER JOIN CMS.ClubMemberships cm ON cm.UserId = u.Id AND cm.IsActive = 1;

        -- =============================================
        -- 7. دریافت باقیمانده هفته قبل
        -- =============================================
        IF @PreviousWeekDefinitionId IS NOT NULL
        BEGIN
            UPDATE b
            SET b.LeftLegCarryover = ISNULL(nb.LeftLegRemainder, 0),
                b.RightLegCarryover = ISNULL(nb.RightLegRemainder, 0)
            FROM #Balances b
            LEFT JOIN CMS.NetworkWeeklyBalances nb ON nb.UserId = b.UserId AND nb.WeekDefinitionId = @PreviousWeekDefinitionId;
        END

        -- =============================================
        -- 8. محاسبه اعضای جدید هر پا با CTE (بهینه!)
        -- =============================================
        
        -- CTE برای شمارش اعضای جدید شاخه چپ
        ;WITH LeftLegCTE AS (
            -- فرزند مستقیم چپ (لول 1)
            SELECT 
                u.NetworkParentId AS ParentId,
                u.Id AS ChildId,
                1 AS Level
            FROM CMS.Users u
            WHERE u.LegPosition = 0 -- Left
            AND u.NetworkParentId IS NOT NULL

            UNION ALL

            -- زیرمجموعه‌های بازگشتی
            SELECT 
                cte.ParentId,
                u.Id AS ChildId,
                cte.Level + 1 AS Level
            FROM CMS.Users u
            INNER JOIN LeftLegCTE cte ON u.NetworkParentId = cte.ChildId
            WHERE cte.Level < @MaxNetworkLevel
        ),
        LeftNewMembers AS (
            SELECT 
                cte.ParentId AS UserId,
                COUNT(*) AS NewMembersCount
            FROM LeftLegCTE cte
            INNER JOIN CMS.ClubMemberships cm ON cm.UserId = cte.ChildId AND cm.IsActive = 1
            WHERE cm.ActivatedAt >= @StartDate AND cm.ActivatedAt <= @EndDate
            GROUP BY cte.ParentId
        )
        UPDATE b
        SET b.LeftLegNewMembers = ISNULL(lnm.NewMembersCount, 0)
        FROM #Balances b
        LEFT JOIN LeftNewMembers lnm ON lnm.UserId = b.UserId
        OPTION (MAXRECURSION 100);

        -- CTE برای شمارش اعضای جدید شاخه راست
        ;WITH RightLegCTE AS (
            -- فرزند مستقیم راست (لول 1)
            SELECT 
                u.NetworkParentId AS ParentId,
                u.Id AS ChildId,
                1 AS Level
            FROM CMS.Users u
            WHERE u.LegPosition = 1 -- Right
            AND u.NetworkParentId IS NOT NULL

            UNION ALL

            -- زیرمجموعه‌های بازگشتی
            SELECT 
                cte.ParentId,
                u.Id AS ChildId,
                cte.Level + 1 AS Level
            FROM CMS.Users u
            INNER JOIN RightLegCTE cte ON u.NetworkParentId = cte.ChildId
            WHERE cte.Level < @MaxNetworkLevel
        ),
        RightNewMembers AS (
            SELECT 
                cte.ParentId AS UserId,
                COUNT(*) AS NewMembersCount
            FROM RightLegCTE cte
            INNER JOIN CMS.ClubMemberships cm ON cm.UserId = cte.ChildId AND cm.IsActive = 1
            WHERE cm.ActivatedAt >= @StartDate AND cm.ActivatedAt <= @EndDate
            GROUP BY cte.ParentId
        )
        UPDATE b
        SET b.RightLegNewMembers = ISNULL(rnm.NewMembersCount, 0)
        FROM #Balances b
        LEFT JOIN RightNewMembers rnm ON rnm.UserId = b.UserId
        OPTION (MAXRECURSION 100);

        -- =============================================
        -- 9. محاسبه تعادل‌ها
        -- =============================================
        UPDATE #Balances
        SET 
            -- مجموع هر پا
            LeftLegTotal = LeftLegNewMembers + LeftLegCarryover,
            RightLegTotal = RightLegNewMembers + RightLegCarryover;

        UPDATE #Balances
        SET
            -- تعادل = MIN(چپ، راست)
            TotalBalances = CASE 
                WHEN LeftLegTotal <= RightLegTotal THEN LeftLegTotal 
                ELSE RightLegTotal 
            END;

        UPDATE #Balances
        SET
            -- باقیمانده برای هفته بعد
            LeftLegRemainder = LeftLegTotal - TotalBalances,
            RightLegRemainder = RightLegTotal - TotalBalances,
            -- فلش (اگر تعادل > سقف)
            FlushedPerSide = CASE 
                WHEN TotalBalances > @MaxBalancesPerLeg THEN TotalBalances - @MaxBalancesPerLeg 
                ELSE 0 
            END;

        UPDATE #Balances
        SET
            -- اعمال سقف
            TotalBalances = CASE 
                WHEN TotalBalances > @MaxBalancesPerLeg THEN @MaxBalancesPerLeg 
                ELSE TotalBalances 
            END,
            -- مجموع فلش از دو طرف
            TotalFlushed = FlushedPerSide * 2;

        -- =============================================
        -- 10. محاسبه تعادل زیرمجموعه با CTE
        -- =============================================
        ;WITH SubordinateCTE AS (
            -- فرزندان مستقیم (لول 1)
            SELECT 
                u.NetworkParentId AS ParentId,
                u.Id AS ChildId,
                1 AS Level
            FROM CMS.Users u
            WHERE u.NetworkParentId IS NOT NULL

            UNION ALL

            -- زیرمجموعه‌های بازگشتی
            SELECT 
                cte.ParentId,
                u.Id AS ChildId,
                cte.Level + 1 AS Level
            FROM CMS.Users u
            INNER JOIN SubordinateCTE cte ON u.NetworkParentId = cte.ChildId
            WHERE cte.Level < @MaxNetworkLevel
        ),
        SubordinateBalances AS (
            SELECT 
                cte.ParentId AS UserId,
                SUM(b.TotalBalances) AS TotalSubordinateBalances
            FROM SubordinateCTE cte
            INNER JOIN #Balances b ON b.UserId = cte.ChildId
            GROUP BY cte.ParentId
        )
        UPDATE b
        SET b.SubordinateBalances = ISNULL(sb.TotalSubordinateBalances, 0)
        FROM #Balances b
        LEFT JOIN SubordinateBalances sb ON sb.UserId = b.UserId
        OPTION (MAXRECURSION 100);

        -- =============================================
        -- 11. Insert نتایج در جدول اصلی
        -- =============================================
        INSERT INTO CMS.NetworkWeeklyBalances (
            UserId,
            WeekDefinitionId,
            LeftLegNewMembers,
            RightLegNewMembers,
            LeftLegCarryover,
            RightLegCarryover,
            LeftLegTotal,
            RightLegTotal,
            TotalBalances,
            LeftLegRemainder,
            RightLegRemainder,
            FlushedPerSide,
            TotalFlushed,
            SubordinateBalances,
            LeftLegBalances,
            RightLegBalances,
            WeeklyPoolContribution,
            CalculatedAt,
            IsExpired,
            IsDeleted,
            Created,
            CreatedBy,
            LastModified,
            LastModifiedBy
        )
        SELECT 
            UserId,
            @WeekDefinitionId,
            LeftLegNewMembers,
            RightLegNewMembers,
            LeftLegCarryover,
            RightLegCarryover,
            LeftLegTotal,
            RightLegTotal,
            TotalBalances,
            LeftLegRemainder,
            RightLegRemainder,
            FlushedPerSide,
            TotalFlushed,
            SubordinateBalances,
            LeftLegTotal,  -- LeftLegBalances (deprecated)
            RightLegTotal, -- RightLegBalances (deprecated)
            0,             -- WeeklyPoolContribution
            @CalculatedAt,
            0,             -- IsExpired
            0,             -- IsDeleted
            @CalculatedAt, -- Created
            'SP',          -- CreatedBy
            @CalculatedAt, -- LastModified
            'SP'           -- LastModifiedBy
        FROM #Balances;

        SET @RowCount = @@ROWCOUNT;

        -- پاکسازی
        DROP TABLE #Balances;

        COMMIT TRANSACTION;

    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;

        -- پاکسازی در صورت خطا
        IF OBJECT_ID('tempdb..#Balances') IS NOT NULL
            DROP TABLE #Balances;

        SET @ErrorMessage = ERROR_MESSAGE();
        RAISERROR(@ErrorMessage, 16, 1);
    END CATCH
END
GO
