package services

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"time"

	"github.com/breakpilot/consent-service/internal/models"

	"github.com/google/uuid"
)

// ========================================
// Student Management
// ========================================

// CreateStudent creates a new student
func (s *SchoolService) CreateStudent(ctx context.Context, schoolID uuid.UUID, req models.CreateStudentRequest) (*models.Student, error) {
	classID, err := uuid.Parse(req.ClassID)
	if err != nil {
		return nil, fmt.Errorf("invalid class ID: %w", err)
	}

	student := &models.Student{
		ID:            uuid.New(),
		SchoolID:      schoolID,
		ClassID:       classID,
		StudentNumber: req.StudentNumber,
		FirstName:     req.FirstName,
		LastName:      req.LastName,
		Gender:        req.Gender,
		IsActive:      true,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	}

	if req.DateOfBirth != nil {
		dob, err := time.Parse("2006-01-02", *req.DateOfBirth)
		if err == nil {
			student.DateOfBirth = &dob
		}
	}

	query := `
		INSERT INTO students (id, school_id, class_id, student_number, first_name, last_name, date_of_birth, gender, is_active, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
		RETURNING id`

	err = s.db.Pool.QueryRow(ctx, query,
		student.ID, student.SchoolID, student.ClassID, student.StudentNumber,
		student.FirstName, student.LastName, student.DateOfBirth, student.Gender,
		student.IsActive, student.CreatedAt, student.UpdatedAt,
	).Scan(&student.ID)

	if err != nil {
		return nil, fmt.Errorf("failed to create student: %w", err)
	}

	return student, nil
}

// GetStudent retrieves a student by ID
func (s *SchoolService) GetStudent(ctx context.Context, studentID uuid.UUID) (*models.Student, error) {
	query := `
		SELECT id, school_id, class_id, user_id, student_number, first_name, last_name, date_of_birth, gender, matrix_user_id, matrix_dm_room, is_active, created_at, updated_at
		FROM students
		WHERE id = $1`

	student := &models.Student{}
	err := s.db.Pool.QueryRow(ctx, query, studentID).Scan(
		&student.ID, &student.SchoolID, &student.ClassID, &student.UserID,
		&student.StudentNumber, &student.FirstName, &student.LastName,
		&student.DateOfBirth, &student.Gender, &student.MatrixUserID,
		&student.MatrixDMRoom, &student.IsActive, &student.CreatedAt, &student.UpdatedAt,
	)

	if err != nil {
		return nil, fmt.Errorf("failed to get student: %w", err)
	}

	return student, nil
}

// ListStudentsByClass lists all students in a class
func (s *SchoolService) ListStudentsByClass(ctx context.Context, classID uuid.UUID) ([]models.Student, error) {
	query := `
		SELECT id, school_id, class_id, user_id, student_number, first_name, last_name, date_of_birth, gender, matrix_user_id, matrix_dm_room, is_active, created_at, updated_at
		FROM students
		WHERE class_id = $1 AND is_active = true
		ORDER BY last_name, first_name`

	rows, err := s.db.Pool.Query(ctx, query, classID)
	if err != nil {
		return nil, fmt.Errorf("failed to list students: %w", err)
	}
	defer rows.Close()

	var students []models.Student
	for rows.Next() {
		var student models.Student
		err := rows.Scan(
			&student.ID, &student.SchoolID, &student.ClassID, &student.UserID,
			&student.StudentNumber, &student.FirstName, &student.LastName,
			&student.DateOfBirth, &student.Gender, &student.MatrixUserID,
			&student.MatrixDMRoom, &student.IsActive, &student.CreatedAt, &student.UpdatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan student: %w", err)
		}
		students = append(students, student)
	}

	return students, nil
}

// ========================================
// Teacher Management
// ========================================

// CreateTeacher creates a new teacher linked to a user account
func (s *SchoolService) CreateTeacher(ctx context.Context, schoolID, userID uuid.UUID, firstName, lastName string, teacherCode, title *string) (*models.Teacher, error) {
	teacher := &models.Teacher{
		ID:          uuid.New(),
		SchoolID:    schoolID,
		UserID:      userID,
		TeacherCode: teacherCode,
		Title:       title,
		FirstName:   firstName,
		LastName:    lastName,
		IsActive:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}

	query := `
		INSERT INTO teachers (id, school_id, user_id, teacher_code, title, first_name, last_name, is_active, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
		RETURNING id`

	err := s.db.Pool.QueryRow(ctx, query,
		teacher.ID, teacher.SchoolID, teacher.UserID, teacher.TeacherCode,
		teacher.Title, teacher.FirstName, teacher.LastName,
		teacher.IsActive, teacher.CreatedAt, teacher.UpdatedAt,
	).Scan(&teacher.ID)

	if err != nil {
		return nil, fmt.Errorf("failed to create teacher: %w", err)
	}

	return teacher, nil
}

// GetTeacher retrieves a teacher by ID
func (s *SchoolService) GetTeacher(ctx context.Context, teacherID uuid.UUID) (*models.Teacher, error) {
	query := `
		SELECT id, school_id, user_id, teacher_code, title, first_name, last_name, matrix_user_id, is_active, created_at, updated_at
		FROM teachers
		WHERE id = $1`

	teacher := &models.Teacher{}
	err := s.db.Pool.QueryRow(ctx, query, teacherID).Scan(
		&teacher.ID, &teacher.SchoolID, &teacher.UserID, &teacher.TeacherCode,
		&teacher.Title, &teacher.FirstName, &teacher.LastName, &teacher.MatrixUserID,
		&teacher.IsActive, &teacher.CreatedAt, &teacher.UpdatedAt,
	)

	if err != nil {
		return nil, fmt.Errorf("failed to get teacher: %w", err)
	}

	return teacher, nil
}

// GetTeacherByUserID retrieves a teacher by their user ID
func (s *SchoolService) GetTeacherByUserID(ctx context.Context, userID uuid.UUID) (*models.Teacher, error) {
	query := `
		SELECT id, school_id, user_id, teacher_code, title, first_name, last_name, matrix_user_id, is_active, created_at, updated_at
		FROM teachers
		WHERE user_id = $1 AND is_active = true`

	teacher := &models.Teacher{}
	err := s.db.Pool.QueryRow(ctx, query, userID).Scan(
		&teacher.ID, &teacher.SchoolID, &teacher.UserID, &teacher.TeacherCode,
		&teacher.Title, &teacher.FirstName, &teacher.LastName, &teacher.MatrixUserID,
		&teacher.IsActive, &teacher.CreatedAt, &teacher.UpdatedAt,
	)

	if err != nil {
		return nil, fmt.Errorf("failed to get teacher by user ID: %w", err)
	}

	return teacher, nil
}

// AssignClassTeacher assigns a teacher to a class
func (s *SchoolService) AssignClassTeacher(ctx context.Context, classID, teacherID uuid.UUID, isPrimary bool) error {
	query := `
		INSERT INTO class_teachers (id, class_id, teacher_id, is_primary, created_at)
		VALUES ($1, $2, $3, $4, $5)
		ON CONFLICT (class_id, teacher_id) DO UPDATE SET is_primary = EXCLUDED.is_primary`

	_, err := s.db.Pool.Exec(ctx, query, uuid.New(), classID, teacherID, isPrimary, time.Now())
	if err != nil {
		return fmt.Errorf("failed to assign class teacher: %w", err)
	}

	return nil
}

// ========================================
// Subject Management
// ========================================

// CreateSubject creates a new subject
func (s *SchoolService) CreateSubject(ctx context.Context, schoolID uuid.UUID, name, shortName string, color *string) (*models.Subject, error) {
	subject := &models.Subject{
		ID:        uuid.New(),
		SchoolID:  schoolID,
		Name:      name,
		ShortName: shortName,
		Color:     color,
		IsActive:  true,
		CreatedAt: time.Now(),
	}

	query := `
		INSERT INTO subjects (id, school_id, name, short_name, color, is_active, created_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
		RETURNING id`

	err := s.db.Pool.QueryRow(ctx, query,
		subject.ID, subject.SchoolID, subject.Name, subject.ShortName,
		subject.Color, subject.IsActive, subject.CreatedAt,
	).Scan(&subject.ID)

	if err != nil {
		return nil, fmt.Errorf("failed to create subject: %w", err)
	}

	return subject, nil
}

// ListSubjects lists all subjects for a school
func (s *SchoolService) ListSubjects(ctx context.Context, schoolID uuid.UUID) ([]models.Subject, error) {
	query := `
		SELECT id, school_id, name, short_name, color, is_active, created_at
		FROM subjects
		WHERE school_id = $1 AND is_active = true
		ORDER BY name`

	rows, err := s.db.Pool.Query(ctx, query, schoolID)
	if err != nil {
		return nil, fmt.Errorf("failed to list subjects: %w", err)
	}
	defer rows.Close()

	var subjects []models.Subject
	for rows.Next() {
		var subject models.Subject
		err := rows.Scan(
			&subject.ID, &subject.SchoolID, &subject.Name, &subject.ShortName,
			&subject.Color, &subject.IsActive, &subject.CreatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan subject: %w", err)
		}
		subjects = append(subjects, subject)
	}

	return subjects, nil
}

// ========================================
// Parent Onboarding
// ========================================

// GenerateParentOnboardingToken generates a QR code token for parent onboarding
func (s *SchoolService) GenerateParentOnboardingToken(ctx context.Context, schoolID, classID, studentID, createdByUserID uuid.UUID, role string) (*models.ParentOnboardingToken, error) {
	// Generate secure random token
	tokenBytes := make([]byte, 32)
	if _, err := rand.Read(tokenBytes); err != nil {
		return nil, fmt.Errorf("failed to generate token: %w", err)
	}
	token := hex.EncodeToString(tokenBytes)

	onboardingToken := &models.ParentOnboardingToken{
		ID:        uuid.New(),
		SchoolID:  schoolID,
		ClassID:   classID,
		StudentID: studentID,
		Token:     token,
		Role:      role,
		ExpiresAt: time.Now().Add(72 * time.Hour), // Valid for 72 hours
		CreatedAt: time.Now(),
		CreatedBy: createdByUserID,
	}

	query := `
		INSERT INTO parent_onboarding_tokens (id, school_id, class_id, student_id, token, role, expires_at, created_at, created_by)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
		RETURNING id`

	err := s.db.Pool.QueryRow(ctx, query,
		onboardingToken.ID, onboardingToken.SchoolID, onboardingToken.ClassID,
		onboardingToken.StudentID, onboardingToken.Token, onboardingToken.Role,
		onboardingToken.ExpiresAt, onboardingToken.CreatedAt, onboardingToken.CreatedBy,
	).Scan(&onboardingToken.ID)

	if err != nil {
		return nil, fmt.Errorf("failed to create onboarding token: %w", err)
	}

	return onboardingToken, nil
}

// ValidateOnboardingToken validates and retrieves info for an onboarding token
func (s *SchoolService) ValidateOnboardingToken(ctx context.Context, token string) (*models.ParentOnboardingToken, error) {
	query := `
		SELECT id, school_id, class_id, student_id, token, role, expires_at, used_at, used_by_user_id, created_at, created_by
		FROM parent_onboarding_tokens
		WHERE token = $1 AND used_at IS NULL AND expires_at > NOW()`

	onboardingToken := &models.ParentOnboardingToken{}
	err := s.db.Pool.QueryRow(ctx, query, token).Scan(
		&onboardingToken.ID, &onboardingToken.SchoolID, &onboardingToken.ClassID,
		&onboardingToken.StudentID, &onboardingToken.Token, &onboardingToken.Role,
		&onboardingToken.ExpiresAt, &onboardingToken.UsedAt, &onboardingToken.UsedByUserID,
		&onboardingToken.CreatedAt, &onboardingToken.CreatedBy,
	)

	if err != nil {
		return nil, fmt.Errorf("invalid or expired token: %w", err)
	}

	return onboardingToken, nil
}

// RedeemOnboardingToken marks a token as used and creates the parent account
func (s *SchoolService) RedeemOnboardingToken(ctx context.Context, token string, userID uuid.UUID) error {
	query := `
		UPDATE parent_onboarding_tokens
		SET used_at = NOW(), used_by_user_id = $1
		WHERE token = $2 AND used_at IS NULL AND expires_at > NOW()`

	result, err := s.db.Pool.Exec(ctx, query, userID, token)
	if err != nil {
		return fmt.Errorf("failed to redeem token: %w", err)
	}

	if result.RowsAffected() == 0 {
		return fmt.Errorf("token not found or already used")
	}

	return nil
}
