package db

import (
	"database/sql"
	"fmt"
	"time"

	"git.leaktechnologies.dev/stu/Goondex/internal/model"
)

// SceneStore handles CRUD operations for scenes
type SceneStore struct {
	db *DB
}

// NewSceneStore creates a new scene store
func NewSceneStore(db *DB) *SceneStore {
	return &SceneStore{db: db}
}

// Create inserts a new scene
func (s *SceneStore) Create(scene *model.Scene) error {
	now := time.Now()
	scene.CreatedAt = now
	scene.UpdatedAt = now

	result, err := s.db.conn.Exec(`
		INSERT INTO scenes (title, code, date, studio_id, description, image_path, image_url, director, url, source, source_id, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, scene.Title, scene.Code, scene.Date, scene.StudioID, scene.Description, scene.ImagePath, scene.ImageURL, scene.Director, scene.URL, scene.Source, scene.SourceID, scene.CreatedAt.Format(time.RFC3339), scene.UpdatedAt.Format(time.RFC3339))

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

	id, err := result.LastInsertId()
	if err != nil {
		return fmt.Errorf("failed to get last insert id: %w", err)
	}

	scene.ID = id
	return nil
}

// GetByID retrieves a scene by ID
func (s *SceneStore) GetByID(id int64) (*model.Scene, error) {
	scene := &model.Scene{}
	var createdAt, updatedAt string

	err := s.db.conn.QueryRow(`
		SELECT id, title, COALESCE(code, ''), COALESCE(date, ''), COALESCE(studio_id, 0), COALESCE(description, ''), COALESCE(image_path, ''), COALESCE(image_url, ''), COALESCE(director, ''), COALESCE(url, ''), COALESCE(source, ''), COALESCE(source_id, ''), created_at, updated_at
		FROM scenes WHERE id = ?
	`, id).Scan(&scene.ID, &scene.Title, &scene.Code, &scene.Date, &scene.StudioID, &scene.Description, &scene.ImagePath, &scene.ImageURL, &scene.Director, &scene.URL, &scene.Source, &scene.SourceID, &createdAt, &updatedAt)

	if err == sql.ErrNoRows {
		return nil, fmt.Errorf("scene not found")
	}
	if err != nil {
		return nil, fmt.Errorf("failed to get scene: %w", err)
	}

	scene.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
	scene.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)

	return scene, nil
}

// Search searches for scenes by title or code
func (s *SceneStore) Search(query string) ([]model.Scene, error) {
	rows, err := s.db.conn.Query(`
		SELECT id, title, COALESCE(code, ''), COALESCE(date, ''), COALESCE(studio_id, 0), COALESCE(description, ''), COALESCE(image_path, ''), COALESCE(image_url, ''), COALESCE(director, ''), COALESCE(url, ''), COALESCE(source, ''), COALESCE(source_id, ''), created_at, updated_at
		FROM scenes
		WHERE title LIKE ? OR COALESCE(code, '') LIKE ?
		ORDER BY date DESC, title
	`, "%"+query+"%", "%"+query+"%")

	if err != nil {
		return nil, fmt.Errorf("failed to search scenes: %w", err)
	}
	defer rows.Close()

	var scenes []model.Scene
	for rows.Next() {
		var scene model.Scene
		var createdAt, updatedAt string

		err := rows.Scan(&scene.ID, &scene.Title, &scene.Code, &scene.Date, &scene.StudioID, &scene.Description, &scene.ImagePath, &scene.ImageURL, &scene.Director, &scene.URL, &scene.Source, &scene.SourceID, &createdAt, &updatedAt)
		if err != nil {
			return nil, fmt.Errorf("failed to scan scene: %w", err)
		}

		scene.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
		scene.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)

		scenes = append(scenes, scene)
	}

	return scenes, nil
}

// Update updates an existing scene
func (s *SceneStore) Update(scene *model.Scene) error {
	scene.UpdatedAt = time.Now()

	result, err := s.db.conn.Exec(`
		UPDATE scenes
		SET title = ?, code = ?, date = ?, studio_id = ?, description = ?, image_path = ?, image_url = ?, director = ?, url = ?, source = ?, source_id = ?, updated_at = ?
		WHERE id = ?
	`, scene.Title, scene.Code, scene.Date, scene.StudioID, scene.Description, scene.ImagePath, scene.ImageURL, scene.Director, scene.URL, scene.Source, scene.SourceID, scene.UpdatedAt.Format(time.RFC3339), scene.ID)

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

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

	if rows == 0 {
		return fmt.Errorf("scene not found")
	}

	return nil
}

// Delete deletes a scene by ID
func (s *SceneStore) Delete(id int64) error {
	result, err := s.db.conn.Exec("DELETE FROM scenes WHERE id = ?", id)
	if err != nil {
		return fmt.Errorf("failed to delete scene: %w", err)
	}

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

	if rows == 0 {
		return fmt.Errorf("scene not found")
	}

	return nil
}

// AddPerformer associates a performer with a scene
func (s *SceneStore) AddPerformer(sceneID, performerID int64) error {
	_, err := s.db.conn.Exec(`
		INSERT OR IGNORE INTO scene_performers (scene_id, performer_id)
		VALUES (?, ?)
	`, sceneID, performerID)

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

	return nil
}

// RemovePerformer removes a performer association from a scene
func (s *SceneStore) RemovePerformer(sceneID, performerID int64) error {
	_, err := s.db.conn.Exec(`
		DELETE FROM scene_performers
		WHERE scene_id = ? AND performer_id = ?
	`, sceneID, performerID)

	if err != nil {
		return fmt.Errorf("failed to remove performer from scene: %w", err)
	}

	return nil
}

// AddTag associates a tag with a scene
func (s *SceneStore) AddTag(sceneID, tagID int64) error {
	return s.AddTagWithConfidence(sceneID, tagID, 1.0, "user", false)
}

// AddTagWithConfidence associates a tag with a scene with ML support
func (s *SceneStore) AddTagWithConfidence(sceneID, tagID int64, confidence float64, source string, verified bool) error {
	verifiedInt := 0
	if verified {
		verifiedInt = 1
	}

	_, err := s.db.conn.Exec(`
		INSERT OR REPLACE INTO scene_tags (scene_id, tag_id, confidence, source, verified, created_at)
		VALUES (?, ?, ?, ?, ?, datetime('now'))
	`, sceneID, tagID, confidence, source, verifiedInt)

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

	return nil
}

// VerifyTag marks a scene tag as human-verified
func (s *SceneStore) VerifyTag(sceneID, tagID int64) error {
	_, err := s.db.conn.Exec(`
		UPDATE scene_tags
		SET verified = 1
		WHERE scene_id = ? AND tag_id = ?
	`, sceneID, tagID)

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

	return nil
}

// RemoveTag removes a tag association from a scene
func (s *SceneStore) RemoveTag(sceneID, tagID int64) error {
	_, err := s.db.conn.Exec(`
		DELETE FROM scene_tags
		WHERE scene_id = ? AND tag_id = ?
	`, sceneID, tagID)

	if err != nil {
		return fmt.Errorf("failed to remove tag from scene: %w", err)
	}

	return nil
}

// GetPerformers retrieves all performers for a scene
func (s *SceneStore) GetPerformers(sceneID int64) ([]model.Performer, error) {
	rows, err := s.db.conn.Query(`
		SELECT p.id, p.name, COALESCE(p.aliases, ''), COALESCE(p.gender, ''),
		       COALESCE(p.birthday, ''), COALESCE(p.nationality, ''),
		       COALESCE(p.source, ''), COALESCE(p.source_id, ''),
		       p.created_at, p.updated_at
		FROM performers p
		INNER JOIN scene_performers sp ON p.id = sp.performer_id
		WHERE sp.scene_id = ?
		ORDER BY p.name
	`, sceneID)

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

	var performers []model.Performer
	for rows.Next() {
		var p model.Performer
		var createdAt, updatedAt string

		err := rows.Scan(&p.ID, &p.Name, &p.Aliases, &p.Gender,
			&p.Birthday, &p.Nationality, &p.Source, &p.SourceID,
			&createdAt, &updatedAt)
		if err != nil {
			return nil, fmt.Errorf("failed to scan performer: %w", err)
		}

		p.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
		p.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)

		performers = append(performers, p)
	}

	return performers, nil
}

// GetTags retrieves all tags for a scene
func (s *SceneStore) GetTags(sceneID int64) ([]model.Tag, error) {
	rows, err := s.db.conn.Query(`
		SELECT t.id, t.name, t.category_id, COALESCE(t.description, ''),
		       COALESCE(t.source, ''), COALESCE(t.source_id, ''),
		       t.created_at, t.updated_at
		FROM tags t
		INNER JOIN scene_tags st ON t.id = st.tag_id
		WHERE st.scene_id = ?
		ORDER BY t.name
	`, sceneID)

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

	var tags []model.Tag
	for rows.Next() {
		var t model.Tag
		var createdAt, updatedAt string

		err := rows.Scan(&t.ID, &t.Name, &t.CategoryID, &t.Description,
			&t.Source, &t.SourceID, &createdAt, &updatedAt)
		if err != nil {
			return nil, fmt.Errorf("failed to scan tag: %w", err)
		}

		t.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
		t.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)

		tags = append(tags, t)
	}

	return tags, nil
}

// Upsert inserts or updates a scene based on source_id
func (s *SceneStore) Upsert(scene *model.Scene) error {
	// Try to find existing scene by source_id
	existing, err := s.GetBySourceID(scene.Source, scene.SourceID)
	if err == nil && existing != nil {
		// Update existing
		scene.ID = existing.ID
		return s.Update(scene)
	}
	// Create new
	return s.Create(scene)
}

// GetBySourceID retrieves a scene by its source and source_id
func (s *SceneStore) GetBySourceID(source, sourceID string) (*model.Scene, error) {
	var scene model.Scene
	var createdAt, updatedAt string

	err := s.db.conn.QueryRow(`
		SELECT id, title, COALESCE(code, ''), COALESCE(date, ''), COALESCE(studio_id, 0),
			COALESCE(description, ''), COALESCE(image_path, ''), COALESCE(image_url, ''),
			COALESCE(director, ''), COALESCE(url, ''), COALESCE(source, ''), COALESCE(source_id, ''),
			created_at, updated_at
		FROM scenes
		WHERE source = ? AND source_id = ?
	`, source, sourceID).Scan(
		&scene.ID, &scene.Title, &scene.Code, &scene.Date, &scene.StudioID,
		&scene.Description, &scene.ImagePath, &scene.ImageURL,
		&scene.Director, &scene.URL, &scene.Source, &scene.SourceID,
		&createdAt, &updatedAt,
	)

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

	scene.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
	scene.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)

	return &scene, nil
}

// GetByPerformer retrieves all scenes featuring a specific performer
func (s *SceneStore) GetByPerformer(performerID int64) ([]model.Scene, error) {
	rows, err := s.db.conn.Query(`
		SELECT DISTINCT s.id, s.title, COALESCE(s.code, ''), COALESCE(s.date, ''), COALESCE(s.studio_id, 0),
			COALESCE(s.description, ''), COALESCE(s.image_path, ''), COALESCE(s.image_url, ''),
			COALESCE(s.director, ''), COALESCE(s.url, ''), COALESCE(s.source, ''), COALESCE(s.source_id, ''),
			s.created_at, s.updated_at
		FROM scenes s
		INNER JOIN scene_performers sp ON s.id = sp.scene_id
		WHERE sp.performer_id = ?
		ORDER BY s.date DESC, s.title ASC
	`, performerID)

	if err != nil {
		return nil, fmt.Errorf("failed to get scenes for performer: %w", err)
	}
	defer rows.Close()

	var scenes []model.Scene
	for rows.Next() {
		var scene model.Scene
		var createdAt, updatedAt string

		err := rows.Scan(
			&scene.ID, &scene.Title, &scene.Code, &scene.Date, &scene.StudioID,
			&scene.Description, &scene.ImagePath, &scene.ImageURL,
			&scene.Director, &scene.URL, &scene.Source, &scene.SourceID,
			&createdAt, &updatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan scene: %w", err)
		}

		scene.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
		scene.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)

		scenes = append(scenes, scene)
	}

	return scenes, nil
}

// GetMovies retrieves all movies that contain this scene
func (s *SceneStore) GetMovies(sceneID int64) ([]model.Movie, error) {
	rows, err := s.db.conn.Query(`
		SELECT m.id, m.title, COALESCE(m.date, ''), COALESCE(m.studio_id, 0),
			COALESCE(m.description, ''), COALESCE(m.director, ''), COALESCE(m.duration, 0),
			COALESCE(m.image_path, ''), COALESCE(m.image_url, ''), COALESCE(m.back_image_url, ''),
			COALESCE(m.url, ''), COALESCE(m.source, ''), COALESCE(m.source_id, ''),
			m.created_at, m.updated_at, COALESCE(ms.scene_number, 0)
		FROM movies m
		INNER JOIN movie_scenes ms ON m.id = ms.movie_id
		WHERE ms.scene_id = ?
		ORDER BY m.date DESC, m.title ASC
	`, sceneID)

	if err != nil {
		return nil, fmt.Errorf("failed to get movies for scene: %w", err)
	}
	defer rows.Close()

	var movies []model.Movie
	for rows.Next() {
		var m model.Movie
		var studioID sql.NullInt64
		var createdAt, updatedAt string
		var sceneNumber int

		err := rows.Scan(
			&m.ID, &m.Title, &m.Date, &studioID,
			&m.Description, &m.Director, &m.Duration,
			&m.ImagePath, &m.ImageURL, &m.BackImageURL,
			&m.URL, &m.Source, &m.SourceID,
			&createdAt, &updatedAt, &sceneNumber,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan movie: %w", err)
		}

		if studioID.Valid && studioID.Int64 > 0 {
			m.StudioID = &studioID.Int64
		}

		m.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
		m.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)

		movies = append(movies, m)
	}

	return movies, nil
}
