import json
import zipfile
import io
import os
import decimal
from django.conf import settings
from django.core.files.storage import default_storage
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import timezone
from .models import *
from django.contrib.auth.models import User

class InstitutionExporter:
    def __init__(self, school_id):
        self.school = School.objects.get(id=school_id)
        self.data = {}

    def export(self):
        # 1. School Data
        self.data['school'] = self._model_to_dict(self.school)
        self.data['school_id'] = self.school.id
        
        # 2. Subject Groups
        groups = SubjectGroup.objects.filter(school=self.school)
        self.data['subject_groups'] = [self._model_to_dict(g) for g in groups]
        
        # 3. Subjects
        subjects = Subject.objects.filter(school=self.school)
        self.data['subjects'] = [self._model_to_dict(s) for s in subjects]
        
        # 4. User Profiles & Auth Users
        profiles = UserProfile.objects.filter(school=self.school)
        self.data['users'] = []
        self.data['profiles'] = []
        for p in profiles:
            self.data['users'].append({
                'id': p.user.id,
                'username': p.user.username,
                'password': p.user.password,
                'email': p.user.email,
                'is_active': p.user.is_active,
                'is_staff': p.user.is_staff
            })
            self.data['profiles'].append(self._model_to_dict(p))
            
        # 5. Student Classes
        classes = StudentClass.objects.filter(school=self.school)
        self.data['classes'] = [self._model_to_dict(c) for c in classes]
        
        # 6. Class Schedules
        schedules = ClassSchedule.objects.filter(school=self.school)
        self.data['schedules'] = [self._model_to_dict(s) for s in schedules]
        
        # 7. Students (and photos)
        students = Student.objects.filter(school=self.school)
        self.data['students'] = [self._model_to_dict(s) for s in students]
        
        # 8. Fee Types & Detection Fees
        self.data['fee_types'] = [self._model_to_dict(ft) for ft in FeeType.objects.filter(school=self.school)]
        self.data['detection_fees'] = [self._model_to_dict(df) for df in DetectionFee.objects.filter(school=self.school)]
        
        # 9. Exams
        self.data['exams'] = [self._model_to_dict(e) for e in Exam.objects.filter(school=self.school)]
        
        # 10. Grade Configs
        self.data['grade_configs'] = [self._model_to_dict(gc) for gc in GradeConfig.objects.filter(school=self.school)]
        
        # 11. Student Fees
        self.data['student_fees'] = [self._model_to_dict(f) for f in StudentFee.objects.filter(student__school=self.school)]
        
        # 12. Fee Transactions & Items
        txns = FeeTransaction.objects.filter(school=self.school)
        self.data['fee_transactions'] = [self._model_to_dict(t) for t in txns]
        self.data['fee_transaction_items'] = [self._model_to_dict(ti) for ti in FeeTransactionItem.objects.filter(transaction__school=self.school)]
        
        # 13. Attendance Logs
        self.data['attendance_logs'] = [self._model_to_dict(l) for l in AttendanceLog.objects.filter(school=self.school)]
        
        # 14. Holiday & Leave
        self.data['holidays'] = [self._model_to_dict(h) for h in Holiday.objects.filter(school=self.school)]
        self.data['leaves'] = [self._model_to_dict(lv) for lv in StudentLeave.objects.filter(school=self.school)]
        
        # 15. Admit Passes
        self.data['admit_passes'] = [self._model_to_dict(ap) for ap in AdmitPass.objects.filter(school=self.school)]
        
        # 16. Result Records
        self.data['results'] = [self._model_to_dict(rr) for rr in ResultRecord.objects.filter(student__school=self.school)]
        
        # 17. Virtual Classes
        self.data['virtual_classes'] = [self._model_to_dict(vc) for vc in VirtualClass.objects.filter(school=self.school)]
        
        # 18. Promotion Records
        self.data['promotions'] = [self._model_to_dict(pr) for pr in PromotionRecord.objects.filter(student__school=self.school)]
        
        # 19. Devices
        self.data['devices'] = [self._model_to_dict(d) for d in Device.objects.filter(school=self.school)]
        
        return self._create_zip()

    def _model_to_dict(self, obj):
        from django.forms.models import model_to_dict
        d = model_to_dict(obj)
        # Handle ManyToMany fields which return IDs but need to be serialized carefully if we want to preserve them
        # model_to_dict handles M2M by returning IDs.
        
        # Extra fields not in model_to_dict (auto_now_add, etc.)
        for field in obj._meta.fields:
            if field.name not in d:
                d[field.name] = getattr(obj, field.name)
                
        # Handle decimal/datetime/files
        from django.db.models.fields.files import FieldFile
        for k, v in d.items():
            if isinstance(v, (decimal.Decimal, datetime.date, datetime.datetime, datetime.time)):
                d[k] = str(v)
            elif isinstance(v, FieldFile):
                d[k] = v.name if v else None
        return d

    def _create_zip(self):
        buffer = io.BytesIO()
        with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
            # Add JSON data
            json_str = json.dumps(self.data, cls=DjangoJSONEncoder)
            zip_file.writestr('institution_data.json', json_str)
            
            # Add Student Photos
            students = Student.objects.filter(school=self.school).exclude(photo='')
            for s in students:
                if s.photo and os.path.exists(s.photo.path):
                    zip_file.write(s.photo.path, f'media/{s.photo.name}')
                    
        buffer.seek(0)
        return buffer

class InstitutionImporter:
    def __init__(self, zip_file_content):
        self.zip_file = zipfile.ZipFile(io.BytesIO(zip_file_content))
        self.data = json.loads(self.zip_file.read('institution_data.json'))
        
        self.id_maps = {
            'User': {}, 'School': {}, 'UserProfile': {}, 'StudentClass': {},
            'Subject': {}, 'SubjectGroup': {}, 'Student': {}, 'FeeType': {},
            'Exam': {}, 'FeeTransaction': {}, 'StudentFee': {}
        }

    def get_preview_info(self):
        school_data = self.data.get('school', {})
        return {
            'name': school_data.get('name'),
            'code': school_data.get('code'),
            'student_count': len(self.data.get('students', [])),
            'attendance_count': len(self.data.get('attendance_logs', [])),
            'fee_count': len(self.data.get('student_fees', [])),
            'exam_count': len(self.data.get('exams', [])),
            'already_exists': School.objects.filter(code=school_data.get('code')).exists()
        }

    def import_data(self):
        # Transaction context recommended but we omit for speed here
        try:
            # 1. School
            school_dict = self.data['school']
            original_school_id = school_dict.pop('id', None)
            # Ensure unique code if being imported to same server
            original_code = school_dict['code']
            if School.objects.filter(code=original_code).exists():
                school_dict['code'] = f"{original_code}_IMP_{timezone.now().strftime('%M%S')}"
            
            school = School.objects.create(**school_dict)
            self.id_maps['School'][original_school_id] = school
            
            # 2. Users & Profiles
            for u_dict in self.data['users']:
                old_id = u_dict.pop('id')
                # Check if username exists
                uname = u_dict['username']
                if User.objects.filter(username=uname).exists():
                    u_dict['username'] = f"{uname}_{school.code}"
                
                user = User.objects.create(**u_dict)
                self.id_maps['User'][old_id] = user
                
            for p_dict in self.data['profiles']:
                old_id = p_dict.pop('id', None)
                user_id = p_dict.pop('user')
                p_dict['user'] = self.id_maps['User'][user_id]
                p_dict['school'] = school
                profile = UserProfile.objects.create(**p_dict)
                self.id_maps['UserProfile'][old_id] = profile
                
            # 3. Subject Groups
            for sg_dict in self.data['subject_groups']:
                old_id = sg_dict.pop('id')
                sg_dict['school'] = school
                sg = SubjectGroup.objects.create(**sg_dict)
                self.id_maps['SubjectGroup'][old_id] = sg
                
            # 4. Subjects
            for s_dict in self.data['subjects']:
                old_id = s_dict.pop('id')
                s_dict['school'] = school
                if s_dict.get('group'):
                    s_dict['group'] = self.id_maps['SubjectGroup'][s_dict['group']]
                subj = Subject.objects.create(**s_dict)
                self.id_maps['Subject'][old_id] = subj
                
            # 5. Student Classes
            for sc_dict in self.data['classes']:
                old_id = sc_dict.pop('id')
                m2m_subjects = sc_dict.pop('subjects', [])
                sc_dict['school'] = school
                if sc_dict.get('teacher'):
                    sc_dict['teacher'] = self.id_maps['UserProfile'].get(sc_dict['teacher'])
                
                cls_obj = StudentClass.objects.create(**sc_dict)
                self.id_maps['StudentClass'][old_id] = cls_obj
                # Restore M2M
                for s_id in m2m_subjects:
                    cls_obj.subjects.add(self.id_maps['Subject'][s_id])
                    
            # 6. Schedules
            for sch_dict in self.data['schedules']:
                sch_dict.pop('id', None)
                sch_dict['school'] = school
                sch_dict['student_class'] = self.id_maps['StudentClass'][sch_dict['student_class']]
                ClassSchedule.objects.create(**sch_dict)
                
            # 7. Students (and photos)
            for std_dict in self.data['students']:
                old_id = std_dict.pop('id')
                m2m_subjs = std_dict.pop('subjects', [])
                photo_path = std_dict.get('photo')
                
                std_dict['school'] = school
                if std_dict.get('student_class'):
                    std_dict['student_class'] = self.id_maps['StudentClass'][std_dict['student_class']]
                
                # IMPORTANT: If photo is in zip, restore it
                if photo_path:
                    # media/students/school_code/filename.jpg
                    try:
                        content = self.zip_file.read(f'media/{photo_path}')
                        std_dict['photo'] = ContentFile(content, name=photo_path.split('/')[-1])
                    except:
                        std_dict['photo'] = None
                
                student = Student.objects.create(**std_dict)
                self.id_maps['Student'][old_id] = student
                for s_id in m2m_subjs:
                    student.subjects.add(self.id_maps['Subject'][s_id])
                    
            # 8. Fees & Fines
            for ft_dict in self.data['fee_types']:
                old_id = ft_dict.pop('id')
                ft_dict['school'] = school
                ft = FeeType.objects.create(**ft_dict)
                self.id_maps['FeeType'][old_id] = ft
                
            for df_dict in self.data['detection_fees']:
                df_dict.pop('id', None)
                df_dict['school'] = school
                DetectionFee.objects.create(**df_dict)
                
            # 9. Exams
            for ex_dict in self.data['exams']:
                old_id = ex_dict.pop('id')
                ex_dict['school'] = school
                exam = Exam.objects.create(**ex_dict)
                self.id_maps['Exam'][old_id] = exam
                
            # 10. GradeConfigs
            for gc_dict in self.data['grade_configs']:
                gc_dict.pop('id', None)
                gc_dict['school'] = school
                GradeConfig.objects.create(**gc_dict)
                
            # 11. Student Fees
            for sf_dict in self.data['student_fees']:
                old_id = sf_dict.pop('id')
                sf_dict['student'] = self.id_maps['Student'][sf_dict['student']]
                sf_dict['fee_type'] = self.id_maps['FeeType'][sf_dict['fee_type']]
                if sf_dict.get('student_class'):
                    sf_dict['student_class'] = self.id_maps['StudentClass'][sf_dict['student_class']]
                sf = StudentFee.objects.create(**sf_dict)
                self.id_maps['StudentFee'][old_id] = sf
                
            # 12. Transactions & Items
            for txn_dict in self.data['fee_transactions']:
                old_id = txn_dict.pop('id')
                txn_dict['school'] = school
                txn_dict['student'] = self.id_maps['Student'][txn_dict['student']]
                if txn_dict.get('student_class'):
                    txn_dict['student_class'] = self.id_maps['StudentClass'][txn_dict['student_class']]
                txn = FeeTransaction.objects.create(**txn_dict)
                self.id_maps['FeeTransaction'][old_id] = txn
                
            for ti_dict in self.data['fee_transaction_items']:
                ti_dict.pop('id', None)
                ti_dict['transaction'] = self.id_maps['FeeTransaction'][ti_dict['transaction']]
                ti_dict['fee'] = self.id_maps['StudentFee'][ti_dict['fee']]
                FeeTransactionItem.objects.create(**ti_dict)
                
            # 13. Attendance & Leaves & Holidays
            for l_dict in self.data['attendance_logs']:
                l_dict.pop('id', None)
                l_dict['school'] = school
                if l_dict.get('student'):
                    l_dict['student'] = self.id_maps['Student'][l_dict['student']]
                if l_dict.get('student_class'):
                    l_dict['student_class'] = self.id_maps['StudentClass'][l_dict['student_class']]
                AttendanceLog.objects.create(**l_dict)
                
            for h_dict in self.data['holidays']:
                h_dict.pop('id', None)
                h_dict['school'] = school
                Holiday.objects.create(**h_dict)
                
            for lv_dict in self.data['leaves']:
                lv_dict.pop('id', None)
                lv_dict['school'] = school
                lv_dict['student'] = self.id_maps['Student'][lv_dict['student']]
                StudentLeave.objects.create(**lv_dict)
                
            # 14. Results
            for r_dict in self.data['results']:
                r_dict.pop('id', None)
                r_dict['student'] = self.id_maps['Student'][r_dict['student']]
                r_dict['exam'] = self.id_maps['Exam'][r_dict['exam']]
                r_dict['subject'] = self.id_maps['Subject'][r_dict['subject']]
                if r_dict.get('student_class'):
                    r_dict['student_class'] = self.id_maps['StudentClass'][r_dict['student_class']]
                ResultRecord.objects.create(**r_dict)
                
            # 15. Promotions
            for pr_dict in self.data['promotions']:
                pr_dict.pop('id', None)
                pr_dict['student'] = self.id_maps['Student'][pr_dict['student']]
                pr_dict['student_class'] = self.id_maps['StudentClass'][pr_dict['student_class']]
                PromotionRecord.objects.create(**pr_dict)
                
            # 16. Admit Passes
            for ap_dict in self.data['admit_passes']:
                ap_dict.pop('id', None)
                ap_dict['school'] = school
                ap_dict['student'] = self.id_maps['Student'][ap_dict['student']]
                AdmitPass.objects.create(**ap_dict)
                
            # 17. Virtual Classes
            for vc_dict in self.data.get('virtual_classes', []):
                vc_dict.pop('id', None)
                m2m_subjs = vc_dict.pop('subjects', [])
                m2m_stds = vc_dict.pop('students', [])
                vc_dict['school'] = school
                vc_dict['student_class'] = self.id_maps['StudentClass'][vc_dict['student_class']]
                if vc_dict.get('teacher'):
                    vc_dict['teacher'] = self.id_maps['UserProfile'].get(vc_dict['teacher'])
                
                vc = VirtualClass.objects.create(**vc_dict)
                for s_id in m2m_subjs:
                    vc.subjects.add(self.id_maps['Subject'][s_id])
                for std_id in m2m_stds:
                    vc.students.add(self.id_maps['Student'][std_id])
                
            # 18. Devices
            for d_dict in self.data['devices']:
                d_dict.pop('id', None)
                d_dict['school'] = school
                if d_dict.get('assigned_class'):
                    d_dict['assigned_class'] = self.id_maps['StudentClass'][d_dict['assigned_class']]
                Device.objects.create(**d_dict)
                
            return True, school
        except Exception as e:
            return False, str(e)
