package workshop

import (
	"context"
	"crypto/rand"
	"encoding/base32"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

// Store handles workshop session data persistence
type Store struct {
	pool *pgxpool.Pool
}

// NewStore creates a new workshop store
func NewStore(pool *pgxpool.Pool) *Store {
	return &Store{pool: pool}
}

// ============================================================================
// Session CRUD Operations
// ============================================================================

// CreateSession creates a new workshop session
func (s *Store) CreateSession(ctx context.Context, session *Session) error {
	session.ID = uuid.New()
	session.CreatedAt = time.Now().UTC()
	session.UpdatedAt = session.CreatedAt
	if session.Status == "" {
		session.Status = SessionStatusDraft
	}
	if session.JoinCode == "" {
		session.JoinCode = generateJoinCode()
	}

	settings, _ := json.Marshal(session.Settings)

	_, err := s.pool.Exec(ctx, `
		INSERT INTO workshop_sessions (
			id, tenant_id, namespace_id,
			title, description, session_type, status,
			wizard_schema, current_step, total_steps,
			assessment_id, roadmap_id, portfolio_id,
			scheduled_start, scheduled_end, actual_start, actual_end,
			join_code, require_auth, allow_anonymous,
			settings,
			created_at, updated_at, created_by
		) VALUES (
			$1, $2, $3,
			$4, $5, $6, $7,
			$8, $9, $10,
			$11, $12, $13,
			$14, $15, $16, $17,
			$18, $19, $20,
			$21,
			$22, $23, $24
		)
	`,
		session.ID, session.TenantID, session.NamespaceID,
		session.Title, session.Description, session.SessionType, string(session.Status),
		session.WizardSchema, session.CurrentStep, session.TotalSteps,
		session.AssessmentID, session.RoadmapID, session.PortfolioID,
		session.ScheduledStart, session.ScheduledEnd, session.ActualStart, session.ActualEnd,
		session.JoinCode, session.RequireAuth, session.AllowAnonymous,
		settings,
		session.CreatedAt, session.UpdatedAt, session.CreatedBy,
	)

	return err
}

// GetSession retrieves a session by ID
func (s *Store) GetSession(ctx context.Context, id uuid.UUID) (*Session, error) {
	var session Session
	var status string
	var settings []byte

	err := s.pool.QueryRow(ctx, `
		SELECT
			id, tenant_id, namespace_id,
			title, description, session_type, status,
			wizard_schema, current_step, total_steps,
			assessment_id, roadmap_id, portfolio_id,
			scheduled_start, scheduled_end, actual_start, actual_end,
			join_code, require_auth, allow_anonymous,
			settings,
			created_at, updated_at, created_by
		FROM workshop_sessions WHERE id = $1
	`, id).Scan(
		&session.ID, &session.TenantID, &session.NamespaceID,
		&session.Title, &session.Description, &session.SessionType, &status,
		&session.WizardSchema, &session.CurrentStep, &session.TotalSteps,
		&session.AssessmentID, &session.RoadmapID, &session.PortfolioID,
		&session.ScheduledStart, &session.ScheduledEnd, &session.ActualStart, &session.ActualEnd,
		&session.JoinCode, &session.RequireAuth, &session.AllowAnonymous,
		&settings,
		&session.CreatedAt, &session.UpdatedAt, &session.CreatedBy,
	)

	if err == pgx.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	session.Status = SessionStatus(status)
	json.Unmarshal(settings, &session.Settings)

	return &session, nil
}

// GetSessionByJoinCode retrieves a session by its join code
func (s *Store) GetSessionByJoinCode(ctx context.Context, code string) (*Session, error) {
	var id uuid.UUID
	err := s.pool.QueryRow(ctx,
		"SELECT id FROM workshop_sessions WHERE join_code = $1",
		strings.ToUpper(code),
	).Scan(&id)

	if err == pgx.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	return s.GetSession(ctx, id)
}

// ListSessions lists sessions for a tenant with optional filters
func (s *Store) ListSessions(ctx context.Context, tenantID uuid.UUID, filters *SessionFilters) ([]Session, error) {
	query := `
		SELECT
			id, tenant_id, namespace_id,
			title, description, session_type, status,
			wizard_schema, current_step, total_steps,
			assessment_id, roadmap_id, portfolio_id,
			scheduled_start, scheduled_end, actual_start, actual_end,
			join_code, require_auth, allow_anonymous,
			settings,
			created_at, updated_at, created_by
		FROM workshop_sessions WHERE tenant_id = $1`

	args := []interface{}{tenantID}
	argIdx := 2

	if filters != nil {
		if filters.Status != "" {
			query += fmt.Sprintf(" AND status = $%d", argIdx)
			args = append(args, string(filters.Status))
			argIdx++
		}
		if filters.SessionType != "" {
			query += fmt.Sprintf(" AND session_type = $%d", argIdx)
			args = append(args, filters.SessionType)
			argIdx++
		}
		if filters.AssessmentID != nil {
			query += fmt.Sprintf(" AND assessment_id = $%d", argIdx)
			args = append(args, *filters.AssessmentID)
			argIdx++
		}
		if filters.CreatedBy != nil {
			query += fmt.Sprintf(" AND created_by = $%d", argIdx)
			args = append(args, *filters.CreatedBy)
			argIdx++
		}
	}

	query += " ORDER BY created_at DESC"

	if filters != nil && filters.Limit > 0 {
		query += fmt.Sprintf(" LIMIT $%d", argIdx)
		args = append(args, filters.Limit)
		argIdx++

		if filters.Offset > 0 {
			query += fmt.Sprintf(" OFFSET $%d", argIdx)
			args = append(args, filters.Offset)
		}
	}

	rows, err := s.pool.Query(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var sessions []Session
	for rows.Next() {
		var session Session
		var status string
		var settings []byte

		err := rows.Scan(
			&session.ID, &session.TenantID, &session.NamespaceID,
			&session.Title, &session.Description, &session.SessionType, &status,
			&session.WizardSchema, &session.CurrentStep, &session.TotalSteps,
			&session.AssessmentID, &session.RoadmapID, &session.PortfolioID,
			&session.ScheduledStart, &session.ScheduledEnd, &session.ActualStart, &session.ActualEnd,
			&session.JoinCode, &session.RequireAuth, &session.AllowAnonymous,
			&settings,
			&session.CreatedAt, &session.UpdatedAt, &session.CreatedBy,
		)
		if err != nil {
			return nil, err
		}

		session.Status = SessionStatus(status)
		json.Unmarshal(settings, &session.Settings)

		sessions = append(sessions, session)
	}

	return sessions, nil
}

// UpdateSession updates a session
func (s *Store) UpdateSession(ctx context.Context, session *Session) error {
	session.UpdatedAt = time.Now().UTC()

	settings, _ := json.Marshal(session.Settings)

	_, err := s.pool.Exec(ctx, `
		UPDATE workshop_sessions SET
			title = $2, description = $3, status = $4,
			wizard_schema = $5, current_step = $6, total_steps = $7,
			scheduled_start = $8, scheduled_end = $9,
			actual_start = $10, actual_end = $11,
			require_auth = $12, allow_anonymous = $13,
			settings = $14,
			updated_at = $15
		WHERE id = $1
	`,
		session.ID, session.Title, session.Description, string(session.Status),
		session.WizardSchema, session.CurrentStep, session.TotalSteps,
		session.ScheduledStart, session.ScheduledEnd,
		session.ActualStart, session.ActualEnd,
		session.RequireAuth, session.AllowAnonymous,
		settings,
		session.UpdatedAt,
	)

	return err
}

// UpdateSessionStatus updates only the session status
func (s *Store) UpdateSessionStatus(ctx context.Context, id uuid.UUID, status SessionStatus) error {
	now := time.Now().UTC()

	query := "UPDATE workshop_sessions SET status = $2, updated_at = $3"

	if status == SessionStatusActive {
		query += ", actual_start = COALESCE(actual_start, $3)"
	} else if status == SessionStatusCompleted || status == SessionStatusCancelled {
		query += ", actual_end = $3"
	}

	query += " WHERE id = $1"

	_, err := s.pool.Exec(ctx, query, id, string(status), now)
	return err
}

// AdvanceStep advances the session to the next step
func (s *Store) AdvanceStep(ctx context.Context, id uuid.UUID) error {
	_, err := s.pool.Exec(ctx, `
		UPDATE workshop_sessions SET
			current_step = current_step + 1,
			updated_at = NOW()
		WHERE id = $1 AND current_step < total_steps
	`, id)
	return err
}

// DeleteSession deletes a session and its related data
func (s *Store) DeleteSession(ctx context.Context, id uuid.UUID) error {
	// Delete in order: comments, responses, step_progress, participants, session
	_, err := s.pool.Exec(ctx, "DELETE FROM workshop_comments WHERE session_id = $1", id)
	if err != nil {
		return err
	}
	_, err = s.pool.Exec(ctx, "DELETE FROM workshop_responses WHERE session_id = $1", id)
	if err != nil {
		return err
	}
	_, err = s.pool.Exec(ctx, "DELETE FROM workshop_step_progress WHERE session_id = $1", id)
	if err != nil {
		return err
	}
	_, err = s.pool.Exec(ctx, "DELETE FROM workshop_participants WHERE session_id = $1", id)
	if err != nil {
		return err
	}
	_, err = s.pool.Exec(ctx, "DELETE FROM workshop_sessions WHERE id = $1", id)
	return err
}

// ============================================================================
// Participant Operations
// ============================================================================

// AddParticipant adds a participant to a session
func (s *Store) AddParticipant(ctx context.Context, p *Participant) error {
	p.ID = uuid.New()
	p.JoinedAt = time.Now().UTC()
	p.IsActive = true
	now := p.JoinedAt
	p.LastActiveAt = &now

	_, err := s.pool.Exec(ctx, `
		INSERT INTO workshop_participants (
			id, session_id, user_id,
			name, email, role, department,
			is_active, last_active_at, joined_at, left_at,
			can_edit, can_comment, can_approve
		) VALUES (
			$1, $2, $3,
			$4, $5, $6, $7,
			$8, $9, $10, $11,
			$12, $13, $14
		)
	`,
		p.ID, p.SessionID, p.UserID,
		p.Name, p.Email, string(p.Role), p.Department,
		p.IsActive, p.LastActiveAt, p.JoinedAt, p.LeftAt,
		p.CanEdit, p.CanComment, p.CanApprove,
	)

	return err
}

// GetParticipant retrieves a participant by ID
func (s *Store) GetParticipant(ctx context.Context, id uuid.UUID) (*Participant, error) {
	var p Participant
	var role string

	err := s.pool.QueryRow(ctx, `
		SELECT
			id, session_id, user_id,
			name, email, role, department,
			is_active, last_active_at, joined_at, left_at,
			can_edit, can_comment, can_approve
		FROM workshop_participants WHERE id = $1
	`, id).Scan(
		&p.ID, &p.SessionID, &p.UserID,
		&p.Name, &p.Email, &role, &p.Department,
		&p.IsActive, &p.LastActiveAt, &p.JoinedAt, &p.LeftAt,
		&p.CanEdit, &p.CanComment, &p.CanApprove,
	)

	if err == pgx.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	p.Role = ParticipantRole(role)
	return &p, nil
}

// ListParticipants lists participants for a session
func (s *Store) ListParticipants(ctx context.Context, sessionID uuid.UUID) ([]Participant, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT
			id, session_id, user_id,
			name, email, role, department,
			is_active, last_active_at, joined_at, left_at,
			can_edit, can_comment, can_approve
		FROM workshop_participants WHERE session_id = $1
		ORDER BY joined_at ASC
	`, sessionID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var participants []Participant
	for rows.Next() {
		var p Participant
		var role string

		err := rows.Scan(
			&p.ID, &p.SessionID, &p.UserID,
			&p.Name, &p.Email, &role, &p.Department,
			&p.IsActive, &p.LastActiveAt, &p.JoinedAt, &p.LeftAt,
			&p.CanEdit, &p.CanComment, &p.CanApprove,
		)
		if err != nil {
			return nil, err
		}

		p.Role = ParticipantRole(role)
		participants = append(participants, p)
	}

	return participants, nil
}

// UpdateParticipantActivity updates the last active timestamp
func (s *Store) UpdateParticipantActivity(ctx context.Context, id uuid.UUID) error {
	_, err := s.pool.Exec(ctx, `
		UPDATE workshop_participants SET
			last_active_at = NOW(),
			is_active = true
		WHERE id = $1
	`, id)
	return err
}

// LeaveSession marks a participant as having left
func (s *Store) LeaveSession(ctx context.Context, participantID uuid.UUID) error {
	now := time.Now().UTC()
	_, err := s.pool.Exec(ctx, `
		UPDATE workshop_participants SET
			is_active = false,
			left_at = $2
		WHERE id = $1
	`, participantID, now)
	return err
}

// UpdateParticipant updates a participant's information
func (s *Store) UpdateParticipant(ctx context.Context, p *Participant) error {
	_, err := s.pool.Exec(ctx, `
		UPDATE workshop_participants SET
			name = $2,
			email = $3,
			role = $4,
			department = $5,
			can_edit = $6,
			can_comment = $7,
			can_approve = $8
		WHERE id = $1
	`,
		p.ID,
		p.Name, p.Email, string(p.Role), p.Department,
		p.CanEdit, p.CanComment, p.CanApprove,
	)
	return err
}

// ============================================================================
// Comment Operations
// ============================================================================

// AddComment adds a comment to a session
func (s *Store) AddComment(ctx context.Context, c *Comment) error {
	c.ID = uuid.New()
	c.CreatedAt = time.Now().UTC()
	c.UpdatedAt = c.CreatedAt

	_, err := s.pool.Exec(ctx, `
		INSERT INTO workshop_comments (
			id, session_id, participant_id,
			step_number, field_id, response_id,
			text, is_resolved,
			created_at, updated_at
		) VALUES (
			$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
		)
	`,
		c.ID, c.SessionID, c.ParticipantID,
		c.StepNumber, c.FieldID, c.ResponseID,
		c.Text, c.IsResolved,
		c.CreatedAt, c.UpdatedAt,
	)

	return err
}

// GetComments retrieves comments for a session
func (s *Store) GetComments(ctx context.Context, sessionID uuid.UUID, stepNumber *int) ([]Comment, error) {
	query := `
		SELECT
			id, session_id, participant_id,
			step_number, field_id, response_id,
			text, is_resolved,
			created_at, updated_at
		FROM workshop_comments WHERE session_id = $1`

	args := []interface{}{sessionID}
	if stepNumber != nil {
		query += " AND step_number = $2"
		args = append(args, *stepNumber)
	}

	query += " ORDER BY created_at ASC"

	rows, err := s.pool.Query(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var comments []Comment
	for rows.Next() {
		var c Comment
		err := rows.Scan(
			&c.ID, &c.SessionID, &c.ParticipantID,
			&c.StepNumber, &c.FieldID, &c.ResponseID,
			&c.Text, &c.IsResolved,
			&c.CreatedAt, &c.UpdatedAt,
		)
		if err != nil {
			return nil, err
		}
		comments = append(comments, c)
	}

	return comments, nil
}

// ============================================================================
// Response Operations
// ============================================================================

// SaveResponse creates or updates a response
func (s *Store) SaveResponse(ctx context.Context, r *Response) error {
	r.UpdatedAt = time.Now().UTC()

	valueJSON, _ := json.Marshal(r.Value)

	// Upsert based on session_id, participant_id, field_id
	_, err := s.pool.Exec(ctx, `
		INSERT INTO workshop_responses (
			id, session_id, participant_id,
			step_number, field_id,
			value, value_type, status,
			created_at, updated_at
		) VALUES (
			$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
		)
		ON CONFLICT (session_id, participant_id, field_id) DO UPDATE SET
			value = $6,
			value_type = $7,
			status = $8,
			updated_at = $10
	`,
		uuid.New(), r.SessionID, r.ParticipantID,
		r.StepNumber, r.FieldID,
		valueJSON, r.ValueType, string(r.Status),
		r.UpdatedAt, r.UpdatedAt,
	)

	return err
}

// GetResponses retrieves responses for a session
func (s *Store) GetResponses(ctx context.Context, sessionID uuid.UUID, stepNumber *int) ([]Response, error) {
	query := `
		SELECT
			id, session_id, participant_id,
			step_number, field_id,
			value, value_type, status,
			reviewed_by, reviewed_at, review_notes,
			created_at, updated_at
		FROM workshop_responses WHERE session_id = $1`

	args := []interface{}{sessionID}
	if stepNumber != nil {
		query += " AND step_number = $2"
		args = append(args, *stepNumber)
	}

	query += " ORDER BY step_number ASC, field_id ASC"

	rows, err := s.pool.Query(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var responses []Response
	for rows.Next() {
		var r Response
		var status string
		var valueJSON []byte

		err := rows.Scan(
			&r.ID, &r.SessionID, &r.ParticipantID,
			&r.StepNumber, &r.FieldID,
			&valueJSON, &r.ValueType, &status,
			&r.ReviewedBy, &r.ReviewedAt, &r.ReviewNotes,
			&r.CreatedAt, &r.UpdatedAt,
		)
		if err != nil {
			return nil, err
		}

		r.Status = ResponseStatus(status)
		json.Unmarshal(valueJSON, &r.Value)

		responses = append(responses, r)
	}

	return responses, nil
}

// ============================================================================
// Step Progress Operations
// ============================================================================

// UpdateStepProgress updates the progress for a step
func (s *Store) UpdateStepProgress(ctx context.Context, sessionID uuid.UUID, stepNumber int, status string, progress int) error {
	now := time.Now().UTC()

	_, err := s.pool.Exec(ctx, `
		INSERT INTO workshop_step_progress (
			id, session_id, step_number,
			status, progress, started_at
		) VALUES (
			$1, $2, $3, $4, $5, $6
		)
		ON CONFLICT (session_id, step_number) DO UPDATE SET
			status = $4,
			progress = $5,
			completed_at = CASE WHEN $4 = 'completed' THEN $6 ELSE NULL END
	`, uuid.New(), sessionID, stepNumber, status, progress, now)

	return err
}

// GetStepProgress retrieves step progress for a session
func (s *Store) GetStepProgress(ctx context.Context, sessionID uuid.UUID) ([]StepProgress, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT
			id, session_id, step_number,
			status, progress,
			started_at, completed_at, notes
		FROM workshop_step_progress WHERE session_id = $1
		ORDER BY step_number ASC
	`, sessionID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var progress []StepProgress
	for rows.Next() {
		var sp StepProgress
		err := rows.Scan(
			&sp.ID, &sp.SessionID, &sp.StepNumber,
			&sp.Status, &sp.Progress,
			&sp.StartedAt, &sp.CompletedAt, &sp.Notes,
		)
		if err != nil {
			return nil, err
		}
		progress = append(progress, sp)
	}

	return progress, nil
}

// ============================================================================
// Statistics
// ============================================================================

// GetSessionStats returns statistics for a session
func (s *Store) GetSessionStats(ctx context.Context, sessionID uuid.UUID) (*SessionStats, error) {
	stats := &SessionStats{
		ResponsesByStep:  make(map[int]int),
		ResponsesByField: make(map[string]int),
	}

	// Participant counts
	s.pool.QueryRow(ctx,
		"SELECT COUNT(*) FROM workshop_participants WHERE session_id = $1",
		sessionID).Scan(&stats.ParticipantCount)

	s.pool.QueryRow(ctx,
		"SELECT COUNT(*) FROM workshop_participants WHERE session_id = $1 AND is_active = true",
		sessionID).Scan(&stats.ActiveParticipants)

	// Response count
	s.pool.QueryRow(ctx,
		"SELECT COUNT(*) FROM workshop_responses WHERE session_id = $1",
		sessionID).Scan(&stats.ResponseCount)

	// Comment count
	s.pool.QueryRow(ctx,
		"SELECT COUNT(*) FROM workshop_comments WHERE session_id = $1",
		sessionID).Scan(&stats.CommentCount)

	// Step progress
	s.pool.QueryRow(ctx,
		"SELECT COUNT(*) FROM workshop_step_progress WHERE session_id = $1 AND status = 'completed'",
		sessionID).Scan(&stats.CompletedSteps)

	s.pool.QueryRow(ctx,
		"SELECT total_steps FROM workshop_sessions WHERE id = $1",
		sessionID).Scan(&stats.TotalSteps)

	// Responses by step
	rows, _ := s.pool.Query(ctx,
		"SELECT step_number, COUNT(*) FROM workshop_responses WHERE session_id = $1 GROUP BY step_number",
		sessionID)
	if rows != nil {
		defer rows.Close()
		for rows.Next() {
			var step, count int
			rows.Scan(&step, &count)
			stats.ResponsesByStep[step] = count
		}
	}

	// Responses by field
	rows, _ = s.pool.Query(ctx,
		"SELECT field_id, COUNT(*) FROM workshop_responses WHERE session_id = $1 GROUP BY field_id",
		sessionID)
	if rows != nil {
		defer rows.Close()
		for rows.Next() {
			var field string
			var count int
			rows.Scan(&field, &count)
			stats.ResponsesByField[field] = count
		}
	}

	// Average progress
	if stats.TotalSteps > 0 {
		stats.AverageProgress = (stats.CompletedSteps * 100) / stats.TotalSteps
	}

	return stats, nil
}

// GetSessionSummary returns a complete session summary
func (s *Store) GetSessionSummary(ctx context.Context, sessionID uuid.UUID) (*SessionSummary, error) {
	session, err := s.GetSession(ctx, sessionID)
	if err != nil || session == nil {
		return nil, err
	}

	participants, err := s.ListParticipants(ctx, sessionID)
	if err != nil {
		return nil, err
	}

	stepProgress, err := s.GetStepProgress(ctx, sessionID)
	if err != nil {
		return nil, err
	}

	var responseCount int
	s.pool.QueryRow(ctx,
		"SELECT COUNT(*) FROM workshop_responses WHERE session_id = $1",
		sessionID).Scan(&responseCount)

	completedSteps := 0
	for _, sp := range stepProgress {
		if sp.Status == "completed" {
			completedSteps++
		}
	}

	progress := 0
	if session.TotalSteps > 0 {
		progress = (completedSteps * 100) / session.TotalSteps
	}

	return &SessionSummary{
		Session:         session,
		Participants:    participants,
		StepProgress:    stepProgress,
		TotalResponses:  responseCount,
		CompletedSteps:  completedSteps,
		OverallProgress: progress,
	}, nil
}

// ============================================================================
// Helpers
// ============================================================================

// generateJoinCode generates a random 6-character join code
func generateJoinCode() string {
	b := make([]byte, 4)
	rand.Read(b)
	code := base32.StdEncoding.EncodeToString(b)[:6]
	return strings.ToUpper(code)
}
