package store

import (
	"database/sql"
	"fmt"
	"krafttrainer/internal/model"
	"strings"
)

// CreateSession startet eine neue Trainingseinheit.
func (s *Store) CreateSession(setID int64) (*model.Session, error) {
	// Set prüfen
	var setName string
	err := s.db.QueryRow(`SELECT name FROM training_sets WHERE id = ? AND deleted_at IS NULL`, setID).Scan(&setName)
	if err == sql.ErrNoRows {
		return nil, fmt.Errorf("Set %d existiert nicht", setID)
	}
	if err != nil {
		return nil, fmt.Errorf("Set prüfen: %w", err)
	}

	result, err := s.db.Exec(`INSERT INTO sessions (set_id) VALUES (?)`, setID)
	if err != nil {
		return nil, fmt.Errorf("Session erstellen: %w", err)
	}

	id, _ := result.LastInsertId()
	return s.GetSession(id)
}

// GetSession gibt eine Session mit allen Logs zurück.
func (s *Store) GetSession(id int64) (*model.Session, error) {
	var sess model.Session
	err := s.db.QueryRow(`
		SELECT s.id, s.set_id, ts.name, s.started_at, s.ended_at, s.note
		FROM sessions s
		JOIN training_sets ts ON ts.id = s.set_id
		WHERE s.id = ?`, id,
	).Scan(&sess.ID, &sess.SetID, &sess.SetName, &sess.StartedAt, &sess.EndedAt, &sess.Note)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("Session abfragen: %w", err)
	}

	logs, err := s.getSessionLogs(id)
	if err != nil {
		return nil, err
	}
	sess.Logs = logs
	return &sess, nil
}

// EndSession beendet eine Session.
func (s *Store) EndSession(id int64, note string) (*model.Session, error) {
	result, err := s.db.Exec(`
		UPDATE sessions SET ended_at = CURRENT_TIMESTAMP, note = ?
		WHERE id = ? AND ended_at IS NULL`, note, id,
	)
	if err != nil {
		return nil, fmt.Errorf("Session beenden: %w", err)
	}
	rows, _ := result.RowsAffected()
	if rows == 0 {
		return nil, nil
	}
	return s.GetSession(id)
}

// ListSessions gibt paginierte Sessions zurück (neueste zuerst).
func (s *Store) ListSessions(limit, offset int) ([]model.Session, error) {
	rows, err := s.db.Query(`
		SELECT s.id, s.set_id, ts.name, s.started_at, s.ended_at, s.note
		FROM sessions s
		JOIN training_sets ts ON ts.id = s.set_id
		ORDER BY s.started_at DESC
		LIMIT ? OFFSET ?`, limit, offset,
	)
	if err != nil {
		return nil, fmt.Errorf("Sessions abfragen: %w", err)
	}
	defer rows.Close()

	var sessions []model.Session
	for rows.Next() {
		var sess model.Session
		if err := rows.Scan(&sess.ID, &sess.SetID, &sess.SetName, &sess.StartedAt, &sess.EndedAt, &sess.Note); err != nil {
			return nil, fmt.Errorf("Session scannen: %w", err)
		}
		sessions = append(sessions, sess)
	}
	if sessions == nil {
		sessions = []model.Session{}
	}
	return sessions, rows.Err()
}

// CreateLog fügt einen Satz zu einer offenen Session hinzu.
func (s *Store) CreateLog(sessionID int64, req *model.CreateLogRequest) (*model.SessionLog, error) {
	// Session offen?
	if err := s.checkSessionOpen(sessionID); err != nil {
		return nil, err
	}

	// Übungsname denormalisiert speichern
	var exerciseName string
	err := s.db.QueryRow(`SELECT name FROM exercises WHERE id = ? AND deleted_at IS NULL`, req.ExerciseID).Scan(&exerciseName)
	if err == sql.ErrNoRows {
		return nil, fmt.Errorf("Übung %d existiert nicht", req.ExerciseID)
	}
	if err != nil {
		return nil, fmt.Errorf("Übung abfragen: %w", err)
	}

	result, err := s.db.Exec(`
		INSERT INTO session_logs (session_id, exercise_id, exercise_name, set_number, weight_kg, reps, note)
		VALUES (?, ?, ?, ?, ?, ?, ?)`,
		sessionID, req.ExerciseID, exerciseName, req.SetNumber, req.WeightKg, req.Reps, req.Note,
	)
	if err != nil {
		if strings.Contains(err.Error(), "UNIQUE constraint") {
			return nil, fmt.Errorf("UNIQUE_VIOLATION: Satz %d für diese Übung existiert bereits", req.SetNumber)
		}
		return nil, fmt.Errorf("Log erstellen: %w", err)
	}

	id, _ := result.LastInsertId()
	return s.getLog(id)
}

// UpdateLog korrigiert einen Satz in einer offenen Session.
func (s *Store) UpdateLog(sessionID, logID int64, req *model.UpdateLogRequest) (*model.SessionLog, error) {
	if err := s.checkSessionOpen(sessionID); err != nil {
		return nil, err
	}

	// Log gehört zur Session?
	var exists bool
	err := s.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM session_logs WHERE id = ? AND session_id = ?)`, logID, sessionID).Scan(&exists)
	if err != nil {
		return nil, fmt.Errorf("Log prüfen: %w", err)
	}
	if !exists {
		return nil, nil
	}

	// Partielle Updates
	updates := []string{}
	args := []any{}
	if req.WeightKg != nil {
		updates = append(updates, "weight_kg = ?")
		args = append(args, *req.WeightKg)
	}
	if req.Reps != nil {
		updates = append(updates, "reps = ?")
		args = append(args, *req.Reps)
	}
	if req.Note != nil {
		updates = append(updates, "note = ?")
		args = append(args, *req.Note)
	}
	if len(updates) == 0 {
		return s.getLog(logID)
	}

	args = append(args, logID)
	_, err = s.db.Exec(
		fmt.Sprintf("UPDATE session_logs SET %s WHERE id = ?", strings.Join(updates, ", ")),
		args...,
	)
	if err != nil {
		return nil, fmt.Errorf("Log aktualisieren: %w", err)
	}
	return s.getLog(logID)
}

// DeleteLog löscht einen Satz aus einer offenen Session.
func (s *Store) DeleteLog(sessionID, logID int64) error {
	if err := s.checkSessionOpen(sessionID); err != nil {
		return err
	}

	result, err := s.db.Exec(`DELETE FROM session_logs WHERE id = ? AND session_id = ?`, logID, sessionID)
	if err != nil {
		return fmt.Errorf("Log löschen: %w", err)
	}
	rows, _ := result.RowsAffected()
	if rows == 0 {
		return sql.ErrNoRows
	}
	return nil
}

// GetLastLog gibt die letzten Werte einer Übung zurück.
func (s *Store) GetLastLog(exerciseID int64) (*model.LastLogResponse, error) {
	var resp model.LastLogResponse
	err := s.db.QueryRow(`
		SELECT weight_kg, reps FROM session_logs
		WHERE exercise_id = ?
		ORDER BY logged_at DESC LIMIT 1`, exerciseID,
	).Scan(&resp.WeightKg, &resp.Reps)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("Letzten Log abfragen: %w", err)
	}
	return &resp, nil
}

// checkSessionOpen prüft ob eine Session offen ist.
func (s *Store) checkSessionOpen(sessionID int64) error {
	var endedAt *string
	err := s.db.QueryRow(`SELECT ended_at FROM sessions WHERE id = ?`, sessionID).Scan(&endedAt)
	if err == sql.ErrNoRows {
		return fmt.Errorf("Session %d existiert nicht", sessionID)
	}
	if err != nil {
		return fmt.Errorf("Session prüfen: %w", err)
	}
	if endedAt != nil {
		return fmt.Errorf("SESSION_CLOSED: Session ist bereits beendet")
	}
	return nil
}

// getLog gibt einen einzelnen Log-Eintrag zurück.
func (s *Store) getLog(id int64) (*model.SessionLog, error) {
	var log model.SessionLog
	err := s.db.QueryRow(`
		SELECT id, session_id, exercise_id, exercise_name, set_number, weight_kg, reps, note, logged_at
		FROM session_logs WHERE id = ?`, id,
	).Scan(&log.ID, &log.SessionID, &log.ExerciseID, &log.ExerciseName, &log.SetNumber, &log.WeightKg, &log.Reps, &log.Note, &log.LoggedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("Log abfragen: %w", err)
	}
	return &log, nil
}

// getSessionLogs gibt alle Logs einer Session zurück.
func (s *Store) getSessionLogs(sessionID int64) ([]model.SessionLog, error) {
	rows, err := s.db.Query(`
		SELECT id, session_id, exercise_id, exercise_name, set_number, weight_kg, reps, note, logged_at
		FROM session_logs
		WHERE session_id = ?
		ORDER BY exercise_id, set_number`, sessionID,
	)
	if err != nil {
		return nil, fmt.Errorf("Logs abfragen: %w", err)
	}
	defer rows.Close()

	var logs []model.SessionLog
	for rows.Next() {
		var log model.SessionLog
		if err := rows.Scan(&log.ID, &log.SessionID, &log.ExerciseID, &log.ExerciseName, &log.SetNumber, &log.WeightKg, &log.Reps, &log.Note, &log.LoggedAt); err != nil {
			return nil, fmt.Errorf("Log scannen: %w", err)
		}
		logs = append(logs, log)
	}
	if logs == nil {
		logs = []model.SessionLog{}
	}
	return logs, rows.Err()
}
