import uuid
from pathlib import Path
import io
from datetime import date

from core.config import get_db, BASE_PATH
from core.security.exceptions import GenericError
from models.profile_registration import ProfileRegistration
from models.course import Course
from schema.profile_registration import ProfileRegistrationCreateSchema,ProfileRegistrationUpdateSchema,ProfileRegistrationResponseSchema

# reportlab for PDF generation
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, PageBreak
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet


class ProfileRegistrationService:

    async def upload_file(self, request):
        if request.file:
            file_extension = Path(request.file.filename).suffix
            new_filename = f"{uuid.uuid4()}{file_extension}"
            file_location = BASE_PATH / f'uploads/{new_filename}'
            content = await request.file.read()
            with open(file_location, "wb") as f:
                f.write(content)
            return new_filename
        return None

    async def create_registration(self, request: ProfileRegistrationCreateSchema,user):

        with get_db() as db:
            try:
                # Create a new registration record
                payload=request.model_dump(exclude=['file'])
                file = await self.upload_file(request)
                if file:
                    payload['file'] = file
                payload['user_id']=user['id']
                payload['user_type']=user['user_type']
                registration = ProfileRegistration(**payload)
                db.add(registration)
                db.commit()
                db.refresh(registration)
                return registration
            except Exception as e:
                db.rollback()
                raise GenericError(status_code=500, exc=f"Could not create registration: {str(e)}")

    def get_registration_by_id(self, registration_id: int,user):
        
        with get_db() as db:
            registration = db.query(ProfileRegistration).filter(ProfileRegistration.id == registration_id,ProfileRegistration.user_type==user["user_type"]).first()
            if not registration:
                raise GenericError(status_code=404, exc="Registration not found")
            return registration

    def get_all_registrations(self,user):

        with get_db() as db:
            registrations = db.query(ProfileRegistration).filter(ProfileRegistration.user_type==user["user_type"]).all()
            for registration in registrations:
                if registration.file:
                    registration.file = f"/uploads/{registration.file}"
            return registrations

    async def update_registration(self, registration_id: int, request: ProfileRegistrationUpdateSchema,user):

        with get_db() as db:
            try:
                # Get the existing registration
                registration = db.query(ProfileRegistration).filter(ProfileRegistration.id == registration_id).first()
                if not registration:
                    raise GenericError(status_code=404, exc="Registration not found")
                
                # Update the registration with new data
                update_data = request.model_dump(exclude_unset=True, exclude=['file'])
                file = await self.upload_file(request)
                if file:
                    update_data['file'] = file
                update_data['user_id']=user['id']
                update_data['user_type']=user['user_type']
                for key, value in update_data.items():
                    setattr(registration, key, value)
                
                db.commit()
                db.refresh(registration)
                return registration
            except Exception as e:
                db.rollback()
                raise GenericError(status_code=500, exc=f"Could not update registration: {str(e)}")

    def delete_registration(self, registration_id: int):
        
        with get_db() as db:
            registration = db.query(ProfileRegistration).filter(ProfileRegistration.id == registration_id).first()
            if not registration:
                raise GenericError(status_code=404, exc="Registration not found")
            
            db.delete(registration)
            db.commit()
            return {"message": "Registration deleted successfully"}

    def list_content_by_course_and_user(self, course_id: int, user_id: int, user_type: str):
        # Validate user_type is either 'parent' or 'participant'
        
        with get_db() as db:
            # Query profile registrations based on course_id, user_id, and user_type
            registrations = db.query(ProfileRegistration).filter(
                ProfileRegistration.course_id == course_id,
                ProfileRegistration.user_id == user_id,
                ProfileRegistration.user_type == user_type
            ).all()
            
            return registrations

    def generate_pdf_by_course(self, course_id: int, participant_id: int, user):
        """Generate a PDF (bytes) containing profile registrations for a given course_id and the user's user_type."""
        try:
            with get_db() as db:
                registrations = db.query(ProfileRegistration).filter(
                    ProfileRegistration.course_id == course_id,
                    ProfileRegistration.participant_id == participant_id
                ).all()

                # Build a map of course_id -> course name so we can replace course_id with course_name
                course_ids = {r.course_id for r in registrations if r.course_id}
                course_name_map = {}
                if course_ids:
                    courses = db.query(Course).filter(Course.id.in_(list(course_ids))).all()
                    course_name_map = {c.id: c.name for c in courses}

            buffer = io.BytesIO()
            doc = SimpleDocTemplate(buffer, pagesize=A4, leftMargin=20, rightMargin=20, topMargin=20, bottomMargin=20)
            styles = getSampleStyleSheet()
            elements = []

            title = Paragraph(f"Wac Nites Form", styles.get('Title'))
            elements.append(title)
            elements.append(Spacer(1, 12))

                # If no registrations found
            if not registrations:
                elements.append(Paragraph("No registrations found for this course.", styles.get('Normal')))
            else:
                # Dynamically generate rows for every column in the model
                # Use label/value pairs with label on the left and value on the right
                cols = [c.name for c in ProfileRegistration.__table__.columns]

                # Exclude these internal fields
                exclude_cols = {'id', 'user_id', 'user_type', 'course_id','file', 'participant_id'}

                label_style = styles.get('BodyText')
                value_style = styles.get('BodyText')

                for idx, r in enumerate(registrations):
                    # Registration heading
                    elements.append(Paragraph(f"Registration ID: {r.id}", styles.get('Heading2')))
                    elements.append(Spacer(1, 6))

                    table_data = []
                    for col in cols:
                        if col in exclude_cols:
                            # replace course_id with course name later
                            if col == 'course_id':
                                # show course name instead
                                label = 'Course Name'
                                course_name = course_name_map.get(r.course_id, '')
                                value = course_name or ''
                            elif col == 'participant_id':
                                label = 'Participant ID'
                                value = str(r.participant_id) if r.participant_id else ''
                            else:
                                continue
                        else:
                            # Skip any computed/relationship attributes
                            try:
                                val = getattr(r, col)
                            except Exception:
                                val = None

                            # Format values
                            if isinstance(val, bool):
                                value = 'Yes' if val else 'No'
                            elif isinstance(val, date):
                                value = val.strftime('%Y-%m-%d')
                            elif val is None:
                                value = ''
                            else:
                                value = str(val)

                            label = col.replace('_', ' ').title()

                        # Use Paragraph for value to enable wrapping
                        label_p = Paragraph(f"<b>{label}</b>", label_style)
                        value_p = Paragraph(value.replace('\n', '<br/>'), value_style)
                        table_data.append([label_p, value_p])

                    table = Table(table_data, colWidths=[140, 380], hAlign='LEFT')
                    table.setStyle(TableStyle([
                        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
                        ('LINEBELOW', (0, 0), (-1, -1), 0.25, colors.lightgrey),
                        ('LEFTPADDING', (0, 0), (-1, -1), 6),
                        ('RIGHTPADDING', (0, 0), (-1, -1), 6),
                    ]))
                    elements.append(table)

                    # Add page break between registrations except after last
                    if idx != len(registrations) - 1:
                        elements.append(PageBreak())

            doc.build(elements)
            buffer.seek(0)
            return buffer.getvalue()
        except GenericError:
            raise
        except Exception as e:
            raise GenericError(status_code=500, exc=f"Could not generate PDF: {str(e)}")
