<?php

namespace Modules\Configuration\Services;

use Modules\Configuration\Models\GradingRule;
use Modules\Configuration\Models\DivisionRule;
use Modules\Academic\Models\AcademicYear;
use Illuminate\Support\Facades\DB;
use Illuminate\Pagination\LengthAwarePaginator;

class ConfigurationService
{
    /**
     * Get grading rules with filters and pagination
     */
    public function getGradingRules(array $filters = []): LengthAwarePaginator
    {
        $query = GradingRule::with('academicYear');

        // Apply filters
        if (!empty($filters['academic_year_id'])) {
            $query->where('academic_year_id', $filters['academic_year_id']);
        }

        if (!empty($filters['search'])) {
            $search = $filters['search'];
            $query->where(function ($q) use ($search) {
                $q->where('grade', 'like', "%{$search}%")
                  ->orWhere('min_marks', 'like', "%{$search}%")
                  ->orWhere('max_marks', 'like', "%{$search}%");
            });
        }

        return $query->orderBy('min_marks', 'desc')->paginate(20);
    }

    /**
     * Get division rules with filters and pagination
     */
    public function getDivisionRules(array $filters = []): LengthAwarePaginator
    {
        $query = DivisionRule::with('academicYear');

        // Apply filters
        if (!empty($filters['academic_year_id'])) {
            $query->where('academic_year_id', $filters['academic_year_id']);
        }

        if (!empty($filters['search'])) {
            $search = $filters['search'];
            $query->where(function ($q) use ($search) {
                $q->where('division', 'like', "%{$search}%")
                  ->orWhere('min_points', 'like', "%{$search}%")
                  ->orWhere('max_points', 'like', "%{$search}%");
            });
        }

        return $query->orderBy('min_points', 'asc')->paginate(20);
    }

    /**
     * Create a new grading rule
     */
    public function createGradingRule(array $data): GradingRule
    {
        // Validate no overlapping ranges
        $this->validateGradingRuleRange($data['academic_year_id'], $data['min_marks'], $data['max_marks']);

        return GradingRule::create($data);
    }

    /**
     * Update grading rule
     */
    public function updateGradingRule(GradingRule $gradingRule, array $data): GradingRule
    {
        // Validate no overlapping ranges (excluding current rule)
        $this->validateGradingRuleRange(
            $data['academic_year_id'] ?? $gradingRule->academic_year_id,
            $data['min_marks'] ?? $gradingRule->min_marks,
            $data['max_marks'] ?? $gradingRule->max_marks,
            $gradingRule->id
        );

        $gradingRule->update($data);
        return $gradingRule;
    }

    /**
     * Create a new division rule
     */
    public function createDivisionRule(array $data): DivisionRule
    {
        // Validate no overlapping ranges
        $this->validateDivisionRuleRange($data['academic_year_id'], $data['min_points'], $data['max_points']);

        return DivisionRule::create($data);
    }

    /**
     * Update division rule
     */
    public function updateDivisionRule(DivisionRule $divisionRule, array $data): DivisionRule
    {
        // Validate no overlapping ranges (excluding current rule)
        $this->validateDivisionRuleRange(
            $data['academic_year_id'] ?? $divisionRule->academic_year_id,
            $data['min_points'] ?? $divisionRule->min_points,
            $data['max_points'] ?? $divisionRule->max_points,
            $divisionRule->id
        );

        $divisionRule->update($data);
        return $divisionRule;
    }

    /**
     * Copy rules from one academic year to another
     */
    public function copyRulesToAcademicYear(int $sourceAcademicYearId, int $targetAcademicYearId): array
    {
        $results = ['grading_rules' => 0, 'division_rules' => 0];

        DB::beginTransaction();
        try {
            // Copy grading rules
            $gradingRules = GradingRule::where('academic_year_id', $sourceAcademicYearId)->get();
            foreach ($gradingRules as $rule) {
                // Check if rule already exists
                $exists = GradingRule::where('academic_year_id', $targetAcademicYearId)
                    ->where('grade', $rule->grade)
                    ->exists();

                if (!$exists) {
                    GradingRule::create([
                        'academic_year_id' => $targetAcademicYearId,
                        'grade' => $rule->grade,
                        'min_marks' => $rule->min_marks,
                        'max_marks' => $rule->max_marks,
                        'points' => $rule->points,
                    ]);
                    $results['grading_rules']++;
                }
            }

            // Copy division rules
            $divisionRules = DivisionRule::where('academic_year_id', $sourceAcademicYearId)->get();
            foreach ($divisionRules as $rule) {
                // Check if rule already exists
                $exists = DivisionRule::where('academic_year_id', $targetAcademicYearId)
                    ->where('division', $rule->division)
                    ->exists();

                if (!$exists) {
                    DivisionRule::create([
                        'academic_year_id' => $targetAcademicYearId,
                        'division' => $rule->division,
                        'min_points' => $rule->min_points,
                        'max_points' => $rule->max_points,
                    ]);
                    $results['division_rules']++;
                }
            }

            DB::commit();
            return $results;

        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }

    /**
     * Get default grading rules template
     */
    public function getDefaultGradingRules(): array
    {
        return [
            ['grade' => 'A+', 'min_marks' => 90, 'max_marks' => 100, 'points' => 4],
            ['grade' => 'A', 'min_marks' => 80, 'max_marks' => 89, 'points' => 3],
            ['grade' => 'B+', 'min_marks' => 70, 'max_marks' => 79, 'points' => 2],
            ['grade' => 'B', 'min_marks' => 60, 'max_marks' => 69, 'points' => 1],
            ['grade' => 'C', 'min_marks' => 50, 'max_marks' => 59, 'points' => 0],
            ['grade' => 'F', 'min_marks' => 0, 'max_marks' => 49, 'points' => 0],
        ];
    }

    /**
     * Get default division rules template
     */
    public function getDefaultDivisionRules(): array
    {
        return [
            ['division' => 'I', 'min_points' => 7, 'max_points' => 17],
            ['division' => 'II', 'min_points' => 18, 'max_points' => 20],
            ['division' => 'III', 'min_points' => 21, 'max_points' => 24],
            ['division' => 'IV', 'min_points' => 25, 'max_points' => 31],
            ['division' => 'FAIL', 'min_points' => 32, 'max_points' => 35],
            ['division' => 'INCOMPLETE', 'min_points' => 0, 'max_points' => 6],
        ];
    }

    /**
     * Create default rules for academic year
     */
    public function createDefaultRules(int $academicYearId): array
    {
        $results = ['grading_rules' => 0, 'division_rules' => 0];

        DB::beginTransaction();
        try {
            // Create default grading rules
            foreach ($this->getDefaultGradingRules() as $ruleData) {
                $exists = GradingRule::where('academic_year_id', $academicYearId)
                    ->where('grade', $ruleData['grade'])
                    ->exists();

                if (!$exists) {
                    GradingRule::create(array_merge($ruleData, ['academic_year_id' => $academicYearId]));
                    $results['grading_rules']++;
                }
            }

            // Create default division rules
            foreach ($this->getDefaultDivisionRules() as $ruleData) {
                $exists = DivisionRule::where('academic_year_id', $academicYearId)
                    ->where('division', $ruleData['division'])
                    ->exists();

                if (!$exists) {
                    DivisionRule::create(array_merge($ruleData, ['academic_year_id' => $academicYearId]));
                    $results['division_rules']++;
                }
            }

            DB::commit();
            return $results;

        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }

    /**
     * Get configuration statistics
     */
    public function getConfigurationStatistics(): array
    {
        $totalGradingRules = GradingRule::count();
        $totalDivisionRules = DivisionRule::count();

        // Rules by academic year
        $rulesByAcademicYear = AcademicYear::withCount(['gradingRules', 'divisionRules'])
            ->orderBy('start_date', 'desc')
            ->get()
            ->map(function ($academicYear) {
                return [
                    'academic_year' => $academicYear->name,
                    'grading_rules_count' => $academicYear->grading_rules_count,
                    'division_rules_count' => $academicYear->division_rules_count,
                ];
            });

        return [
            'total_grading_rules' => $totalGradingRules,
            'total_division_rules' => $totalDivisionRules,
            'rules_by_academic_year' => $rulesByAcademicYear,
        ];
    }

    /**
     * Validate grading rule range doesn't overlap
     */
    private function validateGradingRuleRange(int $academicYearId, int $minMarks, int $maxMarks, int $excludeId = null): void
    {
        if ($minMarks > $maxMarks) {
            throw new \Exception('Minimum marks cannot be greater than maximum marks.');
        }

        $query = GradingRule::where('academic_year_id', $academicYearId)
            ->where(function ($q) use ($minMarks, $maxMarks) {
                $q->whereBetween('min_marks', [$minMarks, $maxMarks])
                  ->orWhereBetween('max_marks', [$minMarks, $maxMarks])
                  ->orWhere(function ($subQ) use ($minMarks, $maxMarks) {
                      $subQ->where('min_marks', '<=', $minMarks)
                           ->where('max_marks', '>=', $maxMarks);
                  });
            });

        if ($excludeId) {
            $query->where('id', '!=', $excludeId);
        }

        if ($query->exists()) {
            throw new \Exception('Marks range overlaps with existing grading rule.');
        }
    }

    /**
     * Validate division rule range doesn't overlap
     */
    private function validateDivisionRuleRange(int $academicYearId, int $minPoints, int $maxPoints, int $excludeId = null): void
    {
        if ($minPoints > $maxPoints) {
            throw new \Exception('Minimum points cannot be greater than maximum points.');
        }

        $query = DivisionRule::where('academic_year_id', $academicYearId)
            ->where(function ($q) use ($minPoints, $maxPoints) {
                $q->whereBetween('min_points', [$minPoints, $maxPoints])
                  ->orWhereBetween('max_points', [$minPoints, $maxPoints])
                  ->orWhere(function ($subQ) use ($minPoints, $maxPoints) {
                      $subQ->where('min_points', '<=', $minPoints)
                           ->where('max_points', '>=', $maxPoints);
                  });
            });

        if ($excludeId) {
            $query->where('id', '!=', $excludeId);
        }

        if ($query->exists()) {
            throw new \Exception('Points range overlaps with existing division rule.');
        }
    }
}
