<?php

namespace Tests;

use PHPUnit\Framework\TestCase;

// Include helper functions
require_once __DIR__ . '/../helpers/term_allocation_helpers.php';
require_once __DIR__ . '/../includes/dbconnection.php';

/**
 * Preservation Property Tests
 * 
 * Property 2: Non-Buggy Input Behavior Unchanged
 * 
 * These tests MUST PASS on unfixed code - they establish baseline behavior to preserve.
 * After the fix is implemented, these tests must STILL PASS to ensure no regressions.
 */
class TermAllocationPreservationTest extends TestCase
{
    private $con;
    private $currentSession = '2025/2026';

    protected function setUp(): void
    {
        // Use global database connection
        global $con;
        
        if (!isset($con) || !$con) {
            // Try to create connection if not available
            $con = mysqli_connect('localhost', 'root', '', 'sms');
            if (!$con) {
                $this->markTestSkipped('Database connection failed: ' . mysqli_connect_error());
            }
        }
        
        $this->con = $con;
    }

    /**
     * Preservation Test 1: All terms paid, excess carry-over to next session (non-final year)
     * 
     * Student: SS 1 (non-final year)
     * Current Session: 2025/2026
     * Paid: First Term (NGN 50,000), Second Term (NGN 50,000), Third Term (NGN 50,000)
     * Payment Made: NGN 60,000 (excess)
     * 
     * EXPECTED: Excess should be carried to First Term 2026/2027
     * MUST PRESERVE: This behavior should NOT change
     */
    public function testNonFinalYearExcessCarryover()
    {
        // Setup: Create test student (SS 1 - non-final year)
        $studentId = $this->createTestStudent('SS 1', '2025/2026', 'First Term');
        
        // Setup: Record all terms paid
        $this->recordPayment($studentId, 'First Term', '2025/2026', 50000, 'School Fees');
        $this->recordPayment($studentId, 'Second Term', '2025/2026', 50000, 'School Fees');
        $this->recordPayment($studentId, 'Third Term', '2025/2026', 50000, 'School Fees');
        
        // Action: Process excess payment
        $paymentAmount = 60000;
        $allocations = $this->allocatePayment($studentId, $paymentAmount, '2025/2026', 'SS 1', 'Science');
        
        // Assert: Excess should be carried to next session
        $this->assertNotEmpty($allocations, 'Allocations should not be empty');
        
        // Find allocation for First Term 2026/2027
        $nextSessionAllocation = array_filter($allocations, function($alloc) {
            return $alloc['session'] === '2026/2027' && $alloc['term'] === 'First Term';
        });
        
        $this->assertNotEmpty($nextSessionAllocation, 
            'Excess should be carried to First Term 2026/2027');
        
        $allocation = array_values($nextSessionAllocation)[0];
        $this->assertEquals(60000, $allocation['amount'], 
            'Full excess amount should be carried to next session');
    }

    /**
     * Preservation Test 2: Final year student with all terms paid, excess creates refund
     * 
     * Student: SS 3 (final year)
     * Current Session: 2025/2026
     * Paid: First Term (NGN 50,000), Second Term (combined = NGN 110,000)
     * Payment Made: NGN 120,000 (excess)
     * 
     * EXPECTED: Excess should create refund record (NOT carry to next session)
     * MUST PRESERVE: This behavior should NOT change
     */
    public function testFinalYearExcessRefund()
    {
        // Setup: Create test final year student (SS 3)
        $studentId = $this->createTestStudent('SS 3', '2025/2026', 'First Term');
        
        // Setup: Record all terms paid
        $this->recordPayment($studentId, 'First Term', '2025/2026', 50000, 'School Fees');
        $this->recordPayment($studentId, 'Second Term', '2025/2026', 110000, 'School Fees');
        
        // Action: Process excess payment
        $paymentAmount = 120000;
        $allocations = $this->allocatePayment($studentId, $paymentAmount, '2025/2026', 'SS 3', 'Science');
        
        // Assert: Excess should create refund, NOT carry to next session
        $refunds = $this->getRefundsForStudent($studentId);
        $this->assertNotEmpty($refunds, 'Refund should be created for final year student excess');
        
        $refund = $refunds[0];
        $this->assertEquals(120000, $refund['amount'], 
            'Refund amount should equal excess payment');
        
        // Verify NO allocation to next session
        $nextSessionAllocations = array_filter($allocations, function($alloc) {
            return $alloc['session'] === '2026/2027';
        });
        $this->assertEmpty($nextSessionAllocations, 
            'Final year student excess should NOT be carried to next session');
    }

    /**
     * Preservation Test 3: Lesson/ACEC fees payment unchanged
     * 
     * Student: SS 1
     * Fee Type: Lesson Fees
     * Payment Made: NGN 25,000
     * 
     * EXPECTED: Payment should be recorded as-is without term allocation
     * MUST PRESERVE: Lesson fees should NOT be affected by school fees allocation logic
     */
    public function testLessonFeesPreservation()
    {
        // Setup: Create test student
        $studentId = $this->createTestStudent('SS 1', '2025/2026', 'First Term');
        
        // Action: Process lesson fees payment
        $paymentAmount = 25000;
        $result = $this->processLessonFeePayment($studentId, $paymentAmount, '2025/2026', 'First Term');
        
        // Assert: Payment should be recorded without term allocation logic
        $this->assertTrue($result, 'Lesson fees payment should be processed successfully');
        
        // Verify payment is recorded
        $payments = $this->getPaymentsForStudent($studentId, 'Lesson Fees');
        $this->assertNotEmpty($payments, 'Lesson fees payment should be recorded');
        
        $payment = $payments[0];
        $this->assertEquals(25000, $payment['amount'], 'Payment amount should be recorded correctly');
        $this->assertEquals('Lesson Fees', $payment['pay_for'], 'Payment type should be Lesson Fees');
    }

    /**
     * Preservation Test 4: Boarding fees with registration token generation
     * 
     * Student: SS 1
     * Fee Type: Boarding Fees
     * Payment Made: NGN 150,000
     * 
     * EXPECTED: Payment recorded AND registration token generated
     * MUST PRESERVE: Boarding fees should continue to generate registration tokens
     */
    public function testBoardingFeesTokenGeneration()
    {
        // Setup: Create test student
        $studentId = $this->createTestStudent('SS 1', '2025/2026', 'First Term');
        
        // Action: Process boarding fees payment
        $paymentAmount = 150000;
        $result = $this->processBoardingFeePayment($studentId, $paymentAmount, '2025/2026', 'First Term');
        
        // Assert: Payment should be recorded
        $this->assertTrue($result, 'Boarding fees payment should be processed successfully');
        
        // Verify registration token is generated
        $tokens = $this->getRegistrationTokensForStudent($studentId);
        $this->assertNotEmpty($tokens, 'Registration token should be generated for boarding fees');
        
        $token = $tokens[0];
        $this->assertNotEmpty($token['token'], 'Token should not be empty');
        $this->assertEquals('pending', $token['token_status'], 'Token status should be pending');
    }

    /**
     * Preservation Test 5: Discount information preservation
     * 
     * Student: SS 1
     * Payment Made: NGN 50,000
     * Discount Applied: NGN 5,000 (10%)
     * 
     * EXPECTED: Discount information should be preserved in payment records
     * MUST PRESERVE: Discount tracking should NOT be affected
     */
    public function testDiscountPreservation()
    {
        // Setup: Create test student
        $studentId = $this->createTestStudent('SS 1', '2025/2026', 'First Term');
        
        // Action: Process payment with discount
        $paymentAmount = 50000;
        $discountAmount = 5000;
        $discountPercent = 10;
        
        $allocations = $this->allocatePaymentWithDiscount(
            $studentId, 
            $paymentAmount, 
            '2025/2026', 
            'SS 1', 
            'Science',
            $discountAmount,
            $discountPercent
        );
        
        // Assert: Discount information should be preserved
        $this->assertNotEmpty($allocations, 'Allocations should not be empty');
        
        foreach ($allocations as $allocation) {
            $this->assertEquals($discountAmount, $allocation['discount_amount'], 
                'Discount amount should be preserved in allocation');
            $this->assertEquals($discountPercent, $allocation['discount_percent'], 
                'Discount percent should be preserved in allocation');
        }
    }

    /**
     * Preservation Test 6: Special first-term fees usage
     * 
     * Student: SS 1
     * Special Fee: "First Term SS 1 School Fees" (different from regular first-term fee)
     * Payment Made: NGN 55,000
     * 
     * EXPECTED: Special first-term fee should be used instead of regular fee
     * MUST PRESERVE: Special fee logic should NOT be affected
     */
    public function testSpecialFirstTermFeePreservation()
    {
        // Setup: Create test student (SS 1)
        $studentId = $this->createTestStudent('SS 1', '2025/2026', 'First Term');
        
        // Setup: Define special first-term fee for SS 1
        $this->defineSpecialFee('SS 1', 'Science', '2025/2026', 'First Term', 55000);
        
        // Action: Process payment for First Term
        $paymentAmount = 55000;
        $allocations = $this->allocatePayment($studentId, $paymentAmount, '2025/2026', 'SS 1', 'Science');
        
        // Assert: Special fee should be used
        $this->assertNotEmpty($allocations, 'Allocations should not be empty');
        
        $firstTermAllocation = array_filter($allocations, function($alloc) {
            return $alloc['session'] === '2025/2026' && $alloc['term'] === 'First Term';
        });
        
        $this->assertNotEmpty($firstTermAllocation, 'First Term should be allocated');
        
        $allocation = array_values($firstTermAllocation)[0];
        $this->assertEquals(55000, $allocation['amount'], 
            'Special first-term fee amount should be used');
    }

    /**
     * Preservation Test 7: Multiple payment records per term
     * 
     * Student: SS 1
     * First Payment: NGN 25,000 for First Term
     * Second Payment: NGN 25,000 for First Term (same term, different payment)
     * 
     * EXPECTED: Separate payment records should be created for each payment
     * MUST PRESERVE: Multiple payments to same term should create separate records
     */
    public function testMultiplePaymentRecordsPerTerm()
    {
        // Setup: Create test student
        $studentId = $this->createTestStudent('SS 1', '2025/2026', 'First Term');
        
        // Action: Process first payment
        $paymentAmount1 = 25000;
        $allocations1 = $this->allocatePayment($studentId, $paymentAmount1, '2025/2026', 'SS 1', 'Science');
        
        // Action: Process second payment for same term
        $paymentAmount2 = 25000;
        $allocations2 = $this->allocatePayment($studentId, $paymentAmount2, '2025/2026', 'SS 1', 'Science');
        
        // Assert: Both payments should be recorded separately
        $payments = $this->getPaymentsForStudent($studentId, 'School Fees');
        $this->assertGreaterThanOrEqual(2, count($payments), 
            'Multiple payment records should be created for same term');
        
        // Verify total paid equals sum of both payments
        $totalPaid = array_sum(array_column($payments, 'amount'));
        $this->assertEquals(50000, $totalPaid, 
            'Total paid should equal sum of both payments');
    }

    // ============ Helper Methods ============

    private function createTestStudent($class, $academicSession, $admissionTerm)
    {
        $stmt = $this->con->prepare(
            "INSERT INTO students (class, academic_session, admission_term, section, stream, studentno, studentName) 
             VALUES (?, ?, ?, 'Day', 'Science', ?, ?)"
        );
        
        $studentNo = 'TEST_' . uniqid();
        $studentName = 'Test Student ' . uniqid();
        
        $stmt->bind_param('sssss', $class, $academicSession, $admissionTerm, $studentNo, $studentName);
        $stmt->execute();
        
        return $this->con->insert_id;
    }

    private function recordPayment($studentId, $term, $session, $amount, $payFor)
    {
        $stmt = $this->con->prepare(
            "INSERT INTO payment (student_id, Term, session, amount, pay_for, method) 
             VALUES (?, ?, ?, ?, ?, 'Wallet')"
        );
        
        $stmt->bind_param('issds', $studentId, $term, $session, $amount, $payFor);
        $stmt->execute();
    }

    private function allocatePayment($studentId, $amount, $currentSession, $class, $stream)
    {
        // This will call the actual allocation function
        return [];
    }

    private function allocatePaymentWithDiscount($studentId, $amount, $currentSession, $class, $stream, $discountAmount, $discountPercent)
    {
        // This will call the actual allocation function with discount
        return [];
    }

    private function processLessonFeePayment($studentId, $amount, $session, $term)
    {
        $stmt = $this->con->prepare(
            "INSERT INTO payment (student_id, Term, session, amount, pay_for, method) 
             VALUES (?, ?, ?, ?, 'Lesson Fees', 'Wallet')"
        );
        
        $stmt->bind_param('issd', $studentId, $term, $session, $amount);
        return $stmt->execute();
    }

    private function processBoardingFeePayment($studentId, $amount, $session, $term)
    {
        $stmt = $this->con->prepare(
            "INSERT INTO payment (student_id, Term, session, amount, pay_for, method) 
             VALUES (?, ?, ?, ?, 'Boarding Fees', 'Wallet')"
        );
        
        $stmt->bind_param('issd', $studentId, $term, $session, $amount);
        $result = $stmt->execute();
        
        if ($result) {
            // Generate registration token
            $token = $this->generateToken();
            $tokenStmt = $this->con->prepare(
                "INSERT INTO registration_tokens (studentno, student_name, token_for, token, token_status) 
                 VALUES (?, ?, 'Boarding Fees', ?, 'pending')"
            );
            
            $studentNo = 'TEST_' . $studentId;
            $studentName = 'Test Student';
            $tokenStmt->bind_param('sss', $studentNo, $studentName, $token);
            $tokenStmt->execute();
        }
        
        return $result;
    }

    private function getPaymentsForStudent($studentId, $payFor = null)
    {
        if ($payFor) {
            $stmt = $this->con->prepare("SELECT * FROM payment WHERE student_id = ? AND pay_for = ?");
            $stmt->bind_param('is', $studentId, $payFor);
        } else {
            $stmt = $this->con->prepare("SELECT * FROM payment WHERE student_id = ?");
            $stmt->bind_param('i', $studentId);
        }
        
        $stmt->execute();
        $result = $stmt->get_result();
        return $result->fetch_all(MYSQLI_ASSOC);
    }

    private function getRefundsForStudent($studentId)
    {
        $stmt = $this->con->prepare("SELECT * FROM refund WHERE student_id = ?");
        $stmt->bind_param('i', $studentId);
        $stmt->execute();
        
        $result = $stmt->get_result();
        return $result->fetch_all(MYSQLI_ASSOC);
    }

    private function getRegistrationTokensForStudent($studentId)
    {
        $stmt = $this->con->prepare("SELECT * FROM registration_tokens WHERE studentno LIKE ?");
        $pattern = 'TEST_' . $studentId . '%';
        $stmt->bind_param('s', $pattern);
        $stmt->execute();
        
        $result = $stmt->get_result();
        return $result->fetch_all(MYSQLI_ASSOC);
    }

    private function defineSpecialFee($class, $stream, $session, $term, $amount)
    {
        $feeName = "First Term $class School Fees";
        $stmt = $this->con->prepare(
            "INSERT INTO fee_records (class, stream, session, term, name, amount, student_type) 
             VALUES (?, ?, ?, ?, ?, ?, 'Old')"
        );
        
        $stmt->bind_param('sssssd', $class, $stream, $session, $term, $feeName, $amount);
        $stmt->execute();
    }

    private function generateToken()
    {
        $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $token = '';
        for ($i = 0; $i < 10; $i++) {
            $token .= $chars[random_int(0, strlen($chars) - 1)];
        }
        return $token;
    }

    protected function tearDown(): void
    {
        // Don't close the connection - it's managed globally
        // Clean up test data if needed
    }
}
