package db

import (
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"
)

// AcademyMemStore provides in-memory storage for academy data
type AcademyMemStore struct {
	mu             sync.RWMutex
	courses        map[string]*AcademyCourseRow
	lessons        map[string]*AcademyLessonRow
	quizQuestions  map[string]*AcademyQuizQuestionRow
	enrollments    map[string]*AcademyEnrollmentRow
	certificates   map[string]*AcademyCertificateRow
	lessonProgress map[string]*AcademyLessonProgressRow
}

// Row types matching the DB schema
type AcademyCourseRow struct {
	ID               string
	TenantID         string
	Title            string
	Description      string
	Category         string
	PassingScore     int
	DurationMinutes  int
	RequiredForRoles []string
	Status           string
	CreatedAt        time.Time
	UpdatedAt        time.Time
}

type AcademyLessonRow struct {
	ID              string
	CourseID        string
	Title           string
	Type            string
	ContentMarkdown string
	VideoURL        string
	AudioURL        string
	SortOrder       int
	DurationMinutes int
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

type AcademyQuizQuestionRow struct {
	ID                 string
	LessonID           string
	Question           string
	Options            []string
	CorrectOptionIndex int
	Explanation        string
	SortOrder          int
	CreatedAt          time.Time
}

type AcademyEnrollmentRow struct {
	ID            string
	TenantID      string
	CourseID      string
	UserID        string
	UserName      string
	UserEmail     string
	Status        string
	Progress      int
	StartedAt     time.Time
	CompletedAt   *time.Time
	CertificateID string
	Deadline      time.Time
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

type AcademyCertificateRow struct {
	ID           string
	TenantID     string
	EnrollmentID string
	CourseID     string
	UserID       string
	UserName     string
	CourseName   string
	Score        int
	IssuedAt     time.Time
	ValidUntil   time.Time
	PdfURL       string
}

type AcademyLessonProgressRow struct {
	ID           string
	EnrollmentID string
	LessonID     string
	Completed    bool
	QuizScore    *int
	CompletedAt  *time.Time
}

type AcademyStatisticsRow struct {
	TotalCourses     int
	TotalEnrollments int
	CompletionRate   float64
	OverdueCount     int
	ByCategory       map[string]int
	ByStatus         map[string]int
}

func NewAcademyMemStore() *AcademyMemStore {
	return &AcademyMemStore{
		courses:        make(map[string]*AcademyCourseRow),
		lessons:        make(map[string]*AcademyLessonRow),
		quizQuestions:  make(map[string]*AcademyQuizQuestionRow),
		enrollments:    make(map[string]*AcademyEnrollmentRow),
		certificates:   make(map[string]*AcademyCertificateRow),
		lessonProgress: make(map[string]*AcademyLessonProgressRow),
	}
}

// generateID creates a simple unique ID
func generateID() string {
	return fmt.Sprintf("%d", time.Now().UnixNano())
}

// ---------------------------------------------------------------------------
// Course CRUD
// ---------------------------------------------------------------------------

// ListCourses returns all courses for a tenant, sorted by UpdatedAt DESC.
func (s *AcademyMemStore) ListCourses(tenantID string) []*AcademyCourseRow {
	s.mu.RLock()
	defer s.mu.RUnlock()

	var result []*AcademyCourseRow
	for _, c := range s.courses {
		if c.TenantID == tenantID {
			result = append(result, c)
		}
	}

	sort.Slice(result, func(i, j int) bool {
		return result[i].UpdatedAt.After(result[j].UpdatedAt)
	})

	return result
}

// GetCourse retrieves a single course by ID.
func (s *AcademyMemStore) GetCourse(id string) (*AcademyCourseRow, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	c, ok := s.courses[id]
	if !ok {
		return nil, fmt.Errorf("course not found: %s", id)
	}
	return c, nil
}

// CreateCourse inserts a new course with auto-generated ID and timestamps.
func (s *AcademyMemStore) CreateCourse(row *AcademyCourseRow) *AcademyCourseRow {
	s.mu.Lock()
	defer s.mu.Unlock()

	now := time.Now()
	row.ID = generateID()
	row.CreatedAt = now
	row.UpdatedAt = now
	s.courses[row.ID] = row
	return row
}

// UpdateCourse partially updates a course. Supported keys: Title, Description,
// Category, PassingScore, DurationMinutes, RequiredForRoles, Status.
func (s *AcademyMemStore) UpdateCourse(id string, updates map[string]interface{}) (*AcademyCourseRow, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	c, ok := s.courses[id]
	if !ok {
		return nil, fmt.Errorf("course not found: %s", id)
	}

	for k, v := range updates {
		switch strings.ToLower(k) {
		case "title":
			if val, ok := v.(string); ok {
				c.Title = val
			}
		case "description":
			if val, ok := v.(string); ok {
				c.Description = val
			}
		case "category":
			if val, ok := v.(string); ok {
				c.Category = val
			}
		case "passingscore", "passing_score":
			switch val := v.(type) {
			case int:
				c.PassingScore = val
			case float64:
				c.PassingScore = int(val)
			}
		case "durationminutes", "duration_minutes":
			switch val := v.(type) {
			case int:
				c.DurationMinutes = val
			case float64:
				c.DurationMinutes = int(val)
			}
		case "requiredforroles", "required_for_roles":
			if val, ok := v.([]string); ok {
				c.RequiredForRoles = val
			}
		case "status":
			if val, ok := v.(string); ok {
				c.Status = val
			}
		}
	}

	c.UpdatedAt = time.Now()
	return c, nil
}

// DeleteCourse removes a course and all related lessons, quiz questions,
// enrollments, certificates, and lesson progress.
func (s *AcademyMemStore) DeleteCourse(id string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if _, ok := s.courses[id]; !ok {
		return fmt.Errorf("course not found: %s", id)
	}

	// Collect lesson IDs for this course
	lessonIDs := make(map[string]bool)
	for lid, l := range s.lessons {
		if l.CourseID == id {
			lessonIDs[lid] = true
		}
	}

	// Delete quiz questions belonging to those lessons
	for qid, q := range s.quizQuestions {
		if lessonIDs[q.LessonID] {
			delete(s.quizQuestions, qid)
		}
	}

	// Delete lessons
	for lid := range lessonIDs {
		delete(s.lessons, lid)
	}

	// Collect enrollment IDs for this course
	enrollmentIDs := make(map[string]bool)
	for eid, e := range s.enrollments {
		if e.CourseID == id {
			enrollmentIDs[eid] = true
		}
	}

	// Delete lesson progress belonging to those enrollments
	for pid, p := range s.lessonProgress {
		if enrollmentIDs[p.EnrollmentID] {
			delete(s.lessonProgress, pid)
		}
	}

	// Delete certificates belonging to those enrollments
	for cid, cert := range s.certificates {
		if cert.CourseID == id {
			delete(s.certificates, cid)
		}
	}

	// Delete enrollments
	for eid := range enrollmentIDs {
		delete(s.enrollments, eid)
	}

	// Delete the course itself
	delete(s.courses, id)

	return nil
}

// ---------------------------------------------------------------------------
// Lesson CRUD
// ---------------------------------------------------------------------------

// ListLessons returns all lessons for a course, sorted by SortOrder ASC.
func (s *AcademyMemStore) ListLessons(courseID string) []*AcademyLessonRow {
	s.mu.RLock()
	defer s.mu.RUnlock()

	var result []*AcademyLessonRow
	for _, l := range s.lessons {
		if l.CourseID == courseID {
			result = append(result, l)
		}
	}

	sort.Slice(result, func(i, j int) bool {
		return result[i].SortOrder < result[j].SortOrder
	})

	return result
}

// GetLesson retrieves a single lesson by ID.
func (s *AcademyMemStore) GetLesson(id string) (*AcademyLessonRow, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	l, ok := s.lessons[id]
	if !ok {
		return nil, fmt.Errorf("lesson not found: %s", id)
	}
	return l, nil
}

// CreateLesson inserts a new lesson with auto-generated ID and timestamps.
func (s *AcademyMemStore) CreateLesson(row *AcademyLessonRow) *AcademyLessonRow {
	s.mu.Lock()
	defer s.mu.Unlock()

	now := time.Now()
	row.ID = generateID()
	row.CreatedAt = now
	row.UpdatedAt = now
	s.lessons[row.ID] = row
	return row
}

// UpdateLesson partially updates a lesson. Supported keys: Title, Type,
// ContentMarkdown, VideoURL, AudioURL, SortOrder, DurationMinutes.
func (s *AcademyMemStore) UpdateLesson(id string, updates map[string]interface{}) (*AcademyLessonRow, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	l, ok := s.lessons[id]
	if !ok {
		return nil, fmt.Errorf("lesson not found: %s", id)
	}

	for k, v := range updates {
		switch strings.ToLower(k) {
		case "title":
			if val, ok := v.(string); ok {
				l.Title = val
			}
		case "type":
			if val, ok := v.(string); ok {
				l.Type = val
			}
		case "contentmarkdown", "content_markdown":
			if val, ok := v.(string); ok {
				l.ContentMarkdown = val
			}
		case "videourl", "video_url":
			if val, ok := v.(string); ok {
				l.VideoURL = val
			}
		case "audiourl", "audio_url":
			if val, ok := v.(string); ok {
				l.AudioURL = val
			}
		case "sortorder", "sort_order":
			switch val := v.(type) {
			case int:
				l.SortOrder = val
			case float64:
				l.SortOrder = int(val)
			}
		case "durationminutes", "duration_minutes":
			switch val := v.(type) {
			case int:
				l.DurationMinutes = val
			case float64:
				l.DurationMinutes = int(val)
			}
		}
	}

	l.UpdatedAt = time.Now()
	return l, nil
}

// DeleteLesson removes a lesson and its quiz questions.
func (s *AcademyMemStore) DeleteLesson(id string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if _, ok := s.lessons[id]; !ok {
		return fmt.Errorf("lesson not found: %s", id)
	}

	// Delete quiz questions belonging to this lesson
	for qid, q := range s.quizQuestions {
		if q.LessonID == id {
			delete(s.quizQuestions, qid)
		}
	}

	delete(s.lessons, id)
	return nil
}

// ---------------------------------------------------------------------------
// Quiz Questions
// ---------------------------------------------------------------------------

// ListQuizQuestions returns all quiz questions for a lesson, sorted by SortOrder ASC.
func (s *AcademyMemStore) ListQuizQuestions(lessonID string) []*AcademyQuizQuestionRow {
	s.mu.RLock()
	defer s.mu.RUnlock()

	var result []*AcademyQuizQuestionRow
	for _, q := range s.quizQuestions {
		if q.LessonID == lessonID {
			result = append(result, q)
		}
	}

	sort.Slice(result, func(i, j int) bool {
		return result[i].SortOrder < result[j].SortOrder
	})

	return result
}

// CreateQuizQuestion inserts a new quiz question with auto-generated ID and timestamp.
func (s *AcademyMemStore) CreateQuizQuestion(row *AcademyQuizQuestionRow) *AcademyQuizQuestionRow {
	s.mu.Lock()
	defer s.mu.Unlock()

	row.ID = generateID()
	row.CreatedAt = time.Now()
	s.quizQuestions[row.ID] = row
	return row
}

// ---------------------------------------------------------------------------
// Enrollments
// ---------------------------------------------------------------------------

// ListEnrollments returns enrollments filtered by tenantID and optionally by courseID.
// If courseID is empty, all enrollments for the tenant are returned.
func (s *AcademyMemStore) ListEnrollments(tenantID string, courseID string) []*AcademyEnrollmentRow {
	s.mu.RLock()
	defer s.mu.RUnlock()

	var result []*AcademyEnrollmentRow
	for _, e := range s.enrollments {
		if e.TenantID != tenantID {
			continue
		}
		if courseID != "" && e.CourseID != courseID {
			continue
		}
		result = append(result, e)
	}

	sort.Slice(result, func(i, j int) bool {
		return result[i].UpdatedAt.After(result[j].UpdatedAt)
	})

	return result
}

// GetEnrollment retrieves a single enrollment by ID.
func (s *AcademyMemStore) GetEnrollment(id string) (*AcademyEnrollmentRow, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	e, ok := s.enrollments[id]
	if !ok {
		return nil, fmt.Errorf("enrollment not found: %s", id)
	}
	return e, nil
}

// CreateEnrollment inserts a new enrollment with auto-generated ID and timestamps.
func (s *AcademyMemStore) CreateEnrollment(row *AcademyEnrollmentRow) *AcademyEnrollmentRow {
	s.mu.Lock()
	defer s.mu.Unlock()

	now := time.Now()
	row.ID = generateID()
	row.CreatedAt = now
	row.UpdatedAt = now
	if row.StartedAt.IsZero() {
		row.StartedAt = now
	}
	s.enrollments[row.ID] = row
	return row
}

// UpdateEnrollment partially updates an enrollment. Supported keys: Status,
// Progress, CompletedAt, CertificateID, Deadline.
func (s *AcademyMemStore) UpdateEnrollment(id string, updates map[string]interface{}) (*AcademyEnrollmentRow, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	e, ok := s.enrollments[id]
	if !ok {
		return nil, fmt.Errorf("enrollment not found: %s", id)
	}

	for k, v := range updates {
		switch strings.ToLower(k) {
		case "status":
			if val, ok := v.(string); ok {
				e.Status = val
			}
		case "progress":
			switch val := v.(type) {
			case int:
				e.Progress = val
			case float64:
				e.Progress = int(val)
			}
		case "completedat", "completed_at":
			if val, ok := v.(*time.Time); ok {
				e.CompletedAt = val
			} else if val, ok := v.(time.Time); ok {
				e.CompletedAt = &val
			}
		case "certificateid", "certificate_id":
			if val, ok := v.(string); ok {
				e.CertificateID = val
			}
		case "deadline":
			if val, ok := v.(time.Time); ok {
				e.Deadline = val
			}
		}
	}

	e.UpdatedAt = time.Now()
	return e, nil
}

// ---------------------------------------------------------------------------
// Certificates
// ---------------------------------------------------------------------------

// GetCertificate retrieves a certificate by ID.
func (s *AcademyMemStore) GetCertificate(id string) (*AcademyCertificateRow, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	cert, ok := s.certificates[id]
	if !ok {
		return nil, fmt.Errorf("certificate not found: %s", id)
	}
	return cert, nil
}

// GetCertificateByEnrollment retrieves a certificate by enrollment ID.
func (s *AcademyMemStore) GetCertificateByEnrollment(enrollmentID string) (*AcademyCertificateRow, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, cert := range s.certificates {
		if cert.EnrollmentID == enrollmentID {
			return cert, nil
		}
	}
	return nil, fmt.Errorf("certificate not found for enrollment: %s", enrollmentID)
}

// CreateCertificate inserts a new certificate with auto-generated ID.
func (s *AcademyMemStore) CreateCertificate(row *AcademyCertificateRow) *AcademyCertificateRow {
	s.mu.Lock()
	defer s.mu.Unlock()

	row.ID = generateID()
	if row.IssuedAt.IsZero() {
		row.IssuedAt = time.Now()
	}
	s.certificates[row.ID] = row
	return row
}

// ---------------------------------------------------------------------------
// Lesson Progress
// ---------------------------------------------------------------------------

// ListLessonProgress returns all progress entries for an enrollment.
func (s *AcademyMemStore) ListLessonProgress(enrollmentID string) []*AcademyLessonProgressRow {
	s.mu.RLock()
	defer s.mu.RUnlock()

	var result []*AcademyLessonProgressRow
	for _, p := range s.lessonProgress {
		if p.EnrollmentID == enrollmentID {
			result = append(result, p)
		}
	}
	return result
}

// UpsertLessonProgress inserts or updates a lesson progress entry.
// Matching is done by EnrollmentID + LessonID composite key.
func (s *AcademyMemStore) UpsertLessonProgress(row *AcademyLessonProgressRow) *AcademyLessonProgressRow {
	s.mu.Lock()
	defer s.mu.Unlock()

	// Look for existing entry with same enrollment_id + lesson_id
	for _, p := range s.lessonProgress {
		if p.EnrollmentID == row.EnrollmentID && p.LessonID == row.LessonID {
			p.Completed = row.Completed
			p.QuizScore = row.QuizScore
			p.CompletedAt = row.CompletedAt
			return p
		}
	}

	// Insert new entry
	row.ID = generateID()
	s.lessonProgress[row.ID] = row
	return row
}

// ---------------------------------------------------------------------------
// Statistics
// ---------------------------------------------------------------------------

// GetStatistics computes aggregate statistics for a tenant.
func (s *AcademyMemStore) GetStatistics(tenantID string) *AcademyStatisticsRow {
	s.mu.RLock()
	defer s.mu.RUnlock()

	stats := &AcademyStatisticsRow{
		ByCategory: make(map[string]int),
		ByStatus:   make(map[string]int),
	}

	// Count courses by category
	for _, c := range s.courses {
		if c.TenantID != tenantID {
			continue
		}
		stats.TotalCourses++
		if c.Category != "" {
			stats.ByCategory[c.Category]++
		}
	}

	// Count enrollments and compute completion rate
	var completedCount int
	now := time.Now()
	for _, e := range s.enrollments {
		if e.TenantID != tenantID {
			continue
		}
		stats.TotalEnrollments++
		stats.ByStatus[e.Status]++

		if e.Status == "completed" {
			completedCount++
		}

		// Overdue: not completed and past deadline
		if e.Status != "completed" && !e.Deadline.IsZero() && now.After(e.Deadline) {
			stats.OverdueCount++
		}
	}

	if stats.TotalEnrollments > 0 {
		stats.CompletionRate = float64(completedCount) / float64(stats.TotalEnrollments) * 100.0
	}

	return stats
}
