from marshmallow import Schema, fields, validate, ValidationError
from src.models.models import (
    UserType, TwoFactorMethod, AttendanceStatus,
    TeachingVerificationMethod, ResourceType, AssignmentType,
    SubmissionStatus
)

class BaseSchema(Schema):
    id = fields.String(dump_only=True)
    created_at = fields.DateTime(dump_only=True)

class UserTypeField(fields.Field):
    def _serialize(self, value, attr, obj, **kwargs):
        if value is None:
            return None
        return value.value

    def _deserialize(self, value, attr, data, **kwargs):
        try:
            return UserType(value)
        except ValueError as error:
            raise ValidationError("Invalid user type") from error


class TwoFactorMethodField(fields.Field):
    def _serialize(self, value, attr, obj, **kwargs):
        if value is None:
            return None
        return value.value

    def _deserialize(self, value, attr, data, **kwargs):
        try:
            return TwoFactorMethod(value)
        except ValueError as error:
            raise ValidationError("Invalid two-factor method") from error

class UserSchema(BaseSchema):
    email = fields.Email(required=True)
    first_name = fields.String(required=True, validate=validate.Length(min=1, max=50))
    last_name = fields.String(required=True, validate=validate.Length(min=1, max=50))
    phone = fields.String(validate=validate.Length(max=20))
    user_type = UserTypeField(required=True)
    is_active = fields.Boolean()
    last_login = fields.DateTime(dump_only=True)
    profile_picture = fields.String()
    gender = fields.String()
    date_of_birth = fields.Date()
    nationality = fields.String(validate=validate.Length(max=50))
    two_factor_enabled = fields.Boolean(dump_only=True)
    two_factor_method = TwoFactorMethodField(dump_only=True)

class UserUpdateSchema(BaseSchema):
    email = fields.Email()
    first_name = fields.String(validate=validate.Length(min=1, max=50))
    last_name = fields.String(validate=validate.Length(min=1, max=50))
    phone = fields.String(validate=validate.Length(max=20))
    profile_picture = fields.String()
    gender = fields.String()
    date_of_birth = fields.Date()
    nationality = fields.String(validate=validate.Length(max=50))

class TutorSchema(UserSchema):
    staff_id = fields.String(validate=validate.Length(max=20))
    department = fields.String(validate=validate.Length(max=100))
    office_location = fields.String(validate=validate.Length(max=100))
    qualification = fields.String(validate=validate.Length(max=100))
    bio = fields.String()
    hourly_rate = fields.Float()
    max_teaching_hours = fields.Integer()
    is_full_time = fields.Boolean()
    specialization = fields.String(validate=validate.Length(max=100))
    years_of_teaching = fields.Integer()

class StudentSchema(UserSchema):
    student_id = fields.String(validate=validate.Length(max=20))
    year_of_study = fields.Integer()
    program = fields.String(validate=validate.Length(max=100))
    enrollment_date = fields.Date()
    graduation_date = fields.Date()
    current_semester = fields.String(validate=validate.Length(max=20))
    student_category = fields.String(validate=validate.Length(max=50))
    guardian_name = fields.String(validate=validate.Length(max=100))
    guardian_contact = fields.String(validate=validate.Length(max=20))
    emergency_contact = fields.String(validate=validate.Length(max=20))
    address = fields.String()
    city = fields.String(validate=validate.Length(max=50))
    country = fields.String(validate=validate.Length(max=50))
    postal_code = fields.String(validate=validate.Length(max=20))
    academic_advisor = fields.String(validate=validate.Length(max=100))
    cumulative_gpa = fields.Float()
    has_scholarship = fields.Boolean()
    scholarship_details = fields.String()
    last_attendance_date = fields.Date()

class SupervisorSchema(UserSchema):
    staff_id = fields.String(validate=validate.Length(max=20))
    department = fields.String(validate=validate.Length(max=100))
    office_location = fields.String(validate=validate.Length(max=100))
    office_hours = fields.String(validate=validate.Length(max=200))
    max_tutors = fields.Integer()
    is_head_of_department = fields.Boolean()
    years_of_experience = fields.Integer()

class LoginSchema(Schema):
    email = fields.Email(required=True)
    password = fields.String(required=True, validate=validate.Length(min=8))
    device_id = fields.String(required=False)
    preferred_role = fields.String(required=False, validate=validate.OneOf(['supervisor', 'tutor']))

class OTPVerifySchema(Schema):
    email = fields.String(required=True)
    otp = fields.String(required=True, validate=validate.Length(min=6, max=6))
    device_id = fields.String()

class PasswordResetRequestSchema(Schema):
    email = fields.Email(required=True)

class PasswordResetVerifySchema(Schema):
    token = fields.String(required=True)

class PasswordResetCompleteSchema(Schema):
    token = fields.String(required=True)
    new_password = fields.String(required=True, validate=validate.Length(min=8))

class TwoFactorEnableSchema(Schema):
    method = fields.String(required=True, validate=validate.OneOf(
        [method.value for method in TwoFactorMethod]
    ))

class NotificationPreferenceSchema(BaseSchema):
    receive_email = fields.Boolean()
    receive_sms = fields.Boolean()
    receive_push = fields.Boolean()
    email_notification_time = fields.Integer()
    sms_notification_time = fields.Integer()
    push_notification_time = fields.Integer()
    notify_attendance_updates = fields.Boolean()
    notify_grade_updates = fields.Boolean()
    notify_schedule_changes = fields.Boolean()
    notify_system_messages = fields.Boolean()

class AttendanceStatusField(fields.Field):
    def _serialize(self, value, attr, obj, **kwargs):
        if value is None:
            return None
        return value.value

    def _deserialize(self, value, attr, data, **kwargs):
        try:
            return AttendanceStatus(value)
        except ValueError as error:
            raise ValidationError("Invalid attendance status") from error

class AttendanceSchema(BaseSchema):
    status = AttendanceStatusField()
    timestamp = fields.DateTime()
    notes = fields.String()
    late_minutes = fields.Integer()
    device_used = fields.String(validate=validate.Length(max=50))
    ip_address = fields.String(validate=validate.Length(max=50))
    is_disputed = fields.Boolean()
    dispute_reason = fields.String()

class TeachingVerificationMethodField(fields.Field):
    def _serialize(self, value, attr, obj, **kwargs):
        if value is None:
            return None
        return value.value

    def _deserialize(self, value, attr, data, **kwargs):
        try:
            return TeachingVerificationMethod(value)
        except ValueError as error:
            raise ValidationError("Invalid verification method") from error

class TeachingSessionSchema(BaseSchema):
    start_datetime = fields.DateTime(required=True)
    end_datetime = fields.DateTime(required=True)
    tutor_checkin_time = fields.DateTime()
    tutor_checkout_time = fields.DateTime()
    tutor_checkin_method = TeachingVerificationMethodField()
    tutor_checkout_method = TeachingVerificationMethodField()
    tutor_location_lat = fields.Float()
    tutor_location_long = fields.Float()
    session_notes = fields.String()
    is_verified = fields.Boolean()

class ResourceTypeField(fields.Field):
    def _serialize(self, value, attr, obj, **kwargs):
        if value is None:
            return None
        return value.value

    def _deserialize(self, value, attr, data, **kwargs):
        try:
            return ResourceType(value)
        except ValueError as error:
            raise ValidationError("Invalid resource type") from error

class CourseResourceSchema(BaseSchema):
    title = fields.String(required=True, validate=validate.Length(max=200))
    description = fields.String()
    resource_type = ResourceTypeField(required=True)
    url = fields.String(validate=validate.Length(max=255))
    file_path = fields.String(validate=validate.Length(max=255))
    file_size = fields.Integer()
    duration = fields.Integer()
    is_published = fields.Boolean()
    views = fields.Integer()
    downloads = fields.Integer()

class AssignmentTypeField(fields.Field):
    def _serialize(self, value, attr, obj, **kwargs):
        if value is None:
            return None
        return value.value

    def _deserialize(self, value, attr, data, **kwargs):
        try:
            return AssignmentType(value)
        except ValueError as error:
            raise ValidationError("Invalid assignment type") from error

class AssignmentSchema(BaseSchema):
    title = fields.String(required=True, validate=validate.Length(max=200))
    description = fields.String()
    assignment_type = AssignmentTypeField(required=True)
    total_points = fields.Float(required=True)
    due_date = fields.DateTime()
    is_published = fields.Boolean()
    submission_instructions = fields.String()
    allowed_formats = fields.String(validate=validate.Length(max=100))
    max_attempts = fields.Integer()
    time_limit = fields.Integer()
    is_group_assignment = fields.Boolean()

class SubmissionStatusField(fields.Field):
    def _serialize(self, value, attr, obj, **kwargs):
        if value is None:
            return None
        return value.value

    def _deserialize(self, value, attr, data, **kwargs):
        try:
            return SubmissionStatus(value)
        except ValueError as error:
            raise ValidationError("Invalid submission status") from error

class AssignmentSubmissionSchema(BaseSchema):
    status = SubmissionStatusField()
    submitted_at = fields.DateTime()
    grade = fields.Float()
    feedback = fields.String()
    attempt_number = fields.Integer()
    late_days = fields.Integer()
    submission_text = fields.String()
    file_path = fields.String(validate=validate.Length(max=255))
    file_size = fields.Integer()

class EnrollmentSchema(BaseSchema):
    enrollment_date = fields.Date()
    status = fields.String(validate=validate.OneOf(['active', 'withdrawn', 'completed', 'failed']))
    completion_date = fields.Date()
    grade = fields.String(validate=validate.Length(max=2))
    attendance_percentage = fields.Float()
    midterm_grade = fields.String(validate=validate.Length(max=2))
    final_exam_grade = fields.String(validate=validate.Length(max=2))
    project_grade = fields.String(validate=validate.Length(max=2))
    comments = fields.String()
    # Accept identifiers in requests without exposing them in responses
    student_id = fields.String(load_only=True)
    course_id = fields.String(load_only=True)
    requester_id = fields.String(load_only=True)
    requester_type = fields.String(load_only=True)

class CourseProgressSchema(BaseSchema):
    completion_percentage = fields.Float()
    last_accessed = fields.DateTime()
    attendance_rate = fields.Float()
    current_grade = fields.String(validate=validate.Length(max=2))
    notes = fields.String()
    assignments_completed = fields.Integer()
    assignments_pending = fields.Integer()
    last_assignment_date = fields.Date()
    next_assignment_due = fields.Date()
    overall_performance = fields.String(
        validate=validate.OneOf(['excellent', 'good', 'average', 'poor'])
    )

class TutorAvailabilitySchema(BaseSchema):
    day_of_week = fields.Integer(validate=validate.Range(min=0, max=6))
    start_time = fields.Time(required=True)
    end_time = fields.Time(required=True)
    is_recurring = fields.Boolean()
    valid_from = fields.Date()
    valid_to = fields.Date()
    is_approved = fields.Boolean()
    availability_type = fields.String(
        validate=validate.OneOf(['teaching', 'office_hours', 'research', 'meeting', 'other'])
    )
    location = fields.String(validate=validate.Length(max=100))
    notes = fields.String()

class TimetableSchema(BaseSchema):
    name = fields.String(validate=validate.Length(max=100))
    description = fields.String()
    is_active = fields.Boolean()
    semester = fields.String(validate=validate.Length(max=20))
    academic_year = fields.String(validate=validate.Length(max=20))
    approval_status = fields.String(
        validate=validate.OneOf(['draft', 'pending', 'approved', 'rejected'])
    )

class TimetableBlockSchema(BaseSchema):
    day_of_week = fields.Integer(validate=validate.Range(min=0, max=6))
    start_time = fields.Time(required=True)
    end_time = fields.Time(required=True)
    room = fields.String(validate=validate.Length(max=50))
    block_type = fields.String(
        validate=validate.OneOf(['lecture', 'lab', 'tutorial', 'seminar', 'workshop'])
    )
    recurring = fields.Boolean()
    start_date = fields.Date()
    end_date = fields.Date()



from marshmallow import Schema, fields, validate, validates_schema, ValidationError
from datetime import datetime
from enum import Enum

class ResourceTypeEnum(Enum):
    document = "document"
    video = "video"
    link = "link"
    presentation = "presentation"
    image = "image"
    audio = "audio"
    archive = "archive"
    other = "other"

class AssignmentTypeEnum(Enum):
    homework = "homework"
    quiz = "quiz"
    project = "project"
    essay = "essay"
    presentation = "presentation"
    lab = "lab"
    exam = "exam"

class SubmissionStatusEnum(Enum):
    not_started = "not_started"
    in_progress = "in_progress"
    submitted = "submitted"
    late = "late"
    graded = "graded"

class AssignmentResourceSchema(Schema):
    id = fields.String(dump_only=True)
    title = fields.String(required=True)
    description = fields.String()
    resource_type = fields.Enum(ResourceTypeEnum, by_value=True, required=True)
    url = fields.String()
    file_path = fields.String()
    is_required = fields.Boolean()
    sample_solution = fields.Boolean()
    grading_criteria = fields.String()

class AssignmentCreateSchema(Schema):
    course_id = fields.String(required=True)
    module_id = fields.String()
    title = fields.String(required=True, validate=validate.Length(min=1, max=200))
    description = fields.String()
    assignment_type = fields.Enum(AssignmentTypeEnum, by_value=True, required=True)
    total_points = fields.Float(required=True, validate=validate.Range(min=0.1))
    due_date = fields.DateTime(required=True)
    submission_instructions = fields.String()
    allowed_formats = fields.String()
    max_attempts = fields.Integer(validate=validate.Range(min=1))
    time_limit = fields.Integer(validate=validate.Range(min=1))
    is_group_assignment = fields.Boolean()
    plagiarism_check_enabled = fields.Boolean()
    late_submission_penalty = fields.Float(validate=validate.Range(min=0))
    feedback_type = fields.String(validate=validate.OneOf(["rubric", "comments", "audio", "video"]))
    is_published = fields.Boolean()
    resources = fields.List(fields.Nested(AssignmentResourceSchema))

    @validates_schema
    def validate_due_date(self, data, **kwargs):
        if 'due_date' in data and data['due_date'] < datetime.utcnow():
            raise ValidationError("Due date must be in the future")

class AssignmentUpdateSchema(Schema):
    title = fields.String(validate=validate.Length(min=1, max=200))
    description = fields.String()
    assignment_type = fields.Enum(AssignmentTypeEnum, by_value=True)
    total_points = fields.Float(validate=validate.Range(min=0.1))
    due_date = fields.DateTime()
    submission_instructions = fields.String()
    allowed_formats = fields.String()
    max_attempts = fields.Integer(validate=validate.Range(min=1))
    time_limit = fields.Integer(validate=validate.Range(min=1))
    is_group_assignment = fields.Boolean()
    plagiarism_check_enabled = fields.Boolean()
    late_submission_penalty = fields.Float(validate=validate.Range(min=0))
    feedback_type = fields.String(validate=validate.OneOf(["rubric", "comments", "audio", "video"]))
    is_published = fields.Boolean()
    resources = fields.List(fields.Nested(AssignmentResourceSchema))

class AssignmentSchema(Schema):
    id = fields.String(dump_only=True)
    course_id = fields.String(required=True)
    module_id = fields.String()
    title = fields.String(required=True)
    description = fields.String()
    assignment_type = fields.Enum(AssignmentTypeEnum, by_value=True, required=True)
    total_points = fields.Float(required=True)
    due_date = fields.DateTime(required=True)
    created_at = fields.DateTime(dump_only=True)
    is_published = fields.Boolean()
    time_limit = fields.Integer()
    max_attempts = fields.Integer()
    late_submission_penalty = fields.Float()
    submission_count = fields.Integer(dump_only=True)
    average_grade = fields.Float(dump_only=True)
    resources_count = fields.Integer(dump_only=True)
    resources = fields.List(fields.Nested(AssignmentResourceSchema), dump_only=True)
    module_title = fields.String(dump_only=True)
    course_title = fields.String(dump_only=True)
    course_code = fields.String(dump_only=True)
    creator_name = fields.String(dump_only=True)

class AssignmentSubmissionSchema(Schema):
    id = fields.String(dump_only=True)
    assignment_id = fields.String(required=True)
    student_id = fields.String(required=True)
    status = fields.Enum(SubmissionStatusEnum, by_value=True, dump_only=True)
    submitted_at = fields.DateTime(dump_only=True)
    grade = fields.Float(validate=validate.Range(min=0))
    feedback = fields.String()
    graded_at = fields.DateTime(dump_only=True)
    attempt_number = fields.Integer(dump_only=True)
    late_days = fields.Integer(dump_only=True)
    submission_text = fields.String()
    file_path = fields.String()
    is_draft = fields.Boolean()
    similarity_score = fields.Float(dump_only=True)
    assignment_title = fields.String(dump_only=True)
    total_points = fields.Float(dump_only=True)
    grade_percentage = fields.Float(dump_only=True)
    student_name = fields.String(dump_only=True)
    student_id_number = fields.String(dump_only=True)

class AssignmentGradeSchema(Schema):
    grade = fields.Float(required=True, validate=validate.Range(min=0))
    feedback = fields.String(required=True, validate=validate.Length(min=1))
    grader_id = fields.String(required=True)

class FeedbackResponseSchema(Schema):
    id = fields.String(dump_only=True)
    submission_id = fields.String(required=True)
    response_text = fields.String(required=True, validate=validate.Length(min=1))
    is_tutor_response = fields.Boolean()
    is_read = fields.Boolean(dump_only=True)
    read_at = fields.DateTime(dump_only=True)
    tutor_response = fields.String(dump_only=True)
    tutor_response_at = fields.DateTime(dump_only=True)
    is_closed = fields.Boolean(dump_only=True)

class AssignmentStatsSchema(Schema):
    assignment_id = fields.String(dump_only=True)
    assignment_title = fields.String(dump_only=True)
    total_students = fields.Integer(dump_only=True)
    submitted_count = fields.Integer(dump_only=True)
    submission_rate = fields.Float(dump_only=True)
    graded_count = fields.Integer(dump_only=True)
    graded_rate = fields.Float(dump_only=True)
    average_grade = fields.Float(dump_only=True)
    max_grade = fields.Float(dump_only=True)
    min_grade = fields.Float(dump_only=True)
    late_count = fields.Integer(dump_only=True)
    late_rate = fields.Float(dump_only=True)
    average_similarity = fields.Float(dump_only=True)
    grade_distribution = fields.Dict(dump_only=True)

class StudentProgressSchema(Schema):
    assignments = fields.List(fields.Dict(), dump_only=True)
    progress = fields.Float(dump_only=True)
    completed_count = fields.Integer(dump_only=True)
    total_count = fields.Integer(dump_only=True)
    average_grade = fields.Float(dump_only=True)
    student_name = fields.String(dump_only=True)
    course_title = fields.String(dump_only=True)
    course_code = fields.String(dump_only=True)


class AdminLoginSchema(Schema):
    email = fields.String()
    password = fields.String()