// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package storage // import "miniflux.app/v2/internal/storage"

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"miniflux.app/v2/internal/model"

	"github.com/lib/pq"
)

// EnclosuresByEntryID returns all enclosures for the given entry.
func (s *Storage) EnclosuresByEntryID(entryID int64) (model.EnclosureList, error) {
	query := `
		SELECT
			id,
			user_id,
			entry_id,
			url,
			size,
			mime_type,
		    media_progression
		FROM
			enclosures
		WHERE
			entry_id = $1
		ORDER BY id ASC
	`

	rows, err := s.db.Query(query, entryID)
	if err != nil {
		return nil, fmt.Errorf(`store: unable to fetch enclosures: %v`, err)
	}
	defer rows.Close()

	enclosures := make(model.EnclosureList, 0)
	for rows.Next() {
		var enclosure model.Enclosure
		err := rows.Scan(
			&enclosure.ID,
			&enclosure.UserID,
			&enclosure.EntryID,
			&enclosure.URL,
			&enclosure.Size,
			&enclosure.MimeType,
			&enclosure.MediaProgression,
		)

		if err != nil {
			return nil, fmt.Errorf(`store: unable to fetch enclosure row: %v`, err)
		}

		enclosures = append(enclosures, &enclosure)
	}

	return enclosures, nil
}

// EnclosuresByEntryIDs returns enclosures for the given entries, grouped by entry ID.
func (s *Storage) EnclosuresByEntryIDs(entryIDs []int64) (map[int64]model.EnclosureList, error) {
	query := `
		SELECT
			id,
			user_id,
			entry_id,
			url,
			size,
			mime_type,
		    media_progression
		FROM
			enclosures
		WHERE
			entry_id = ANY($1)
		ORDER BY id ASC
	`

	rows, err := s.db.Query(query, pq.Array(entryIDs))
	if err != nil {
		return nil, fmt.Errorf("store: unable to fetch enclosures: %w", err)
	}
	defer rows.Close()

	enclosuresMap := make(map[int64]model.EnclosureList)
	for rows.Next() {
		var enclosure model.Enclosure
		err := rows.Scan(
			&enclosure.ID,
			&enclosure.UserID,
			&enclosure.EntryID,
			&enclosure.URL,
			&enclosure.Size,
			&enclosure.MimeType,
			&enclosure.MediaProgression,
		)
		if err != nil {
			return nil, fmt.Errorf("store: unable to scan enclosure row: %w", err)
		}

		enclosuresMap[enclosure.EntryID] = append(enclosuresMap[enclosure.EntryID], &enclosure)
	}

	return enclosuresMap, nil
}

// EnclosureByID returns the enclosure for the given user and enclosure ID.
func (s *Storage) EnclosureByID(userID, enclosureID int64) (*model.Enclosure, error) {
	query := `
		SELECT
			id,
			user_id,
			entry_id,
			url,
			size,
			mime_type,
		    media_progression
		FROM
			enclosures
		WHERE
			id = $1 AND user_id = $2
	`

	row := s.db.QueryRow(query, enclosureID, userID)

	var enclosure model.Enclosure
	err := row.Scan(
		&enclosure.ID,
		&enclosure.UserID,
		&enclosure.EntryID,
		&enclosure.URL,
		&enclosure.Size,
		&enclosure.MimeType,
		&enclosure.MediaProgression,
	)

	if errors.Is(err, sql.ErrNoRows) {
		return nil, nil
	} else if err != nil {
		return nil, fmt.Errorf(`store: unable to fetch enclosure row: %v`, err)
	}

	return &enclosure, nil
}

func (s *Storage) createEnclosure(tx *sql.Tx, enclosure *model.Enclosure) error {
	enclosureURL := strings.TrimSpace(enclosure.URL)
	if enclosureURL == "" {
		return nil
	}

	query := `
		INSERT INTO enclosures
			(url, size, mime_type, entry_id, user_id, media_progression)
		VALUES
			($1, $2, $3, $4, $5, $6)
		ON CONFLICT (user_id, entry_id, encode(sha256(url::bytea), 'hex')) DO NOTHING
		RETURNING
			id
	`
	if err := tx.QueryRow(
		query,
		enclosureURL,
		enclosure.Size,
		enclosure.MimeType,
		enclosure.EntryID,
		enclosure.UserID,
		enclosure.MediaProgression,
	).Scan(&enclosure.ID); err != nil && err != sql.ErrNoRows {
		return fmt.Errorf(`store: unable to create enclosure: %w`, err)
	}

	return nil
}

func (s *Storage) updateEnclosures(tx *sql.Tx, entry *model.Entry) error {
	if len(entry.Enclosures) == 0 {
		// Do not keep any old enclosures if there is none in the updated entry.
		query := `
			DELETE FROM
				enclosures
			WHERE
				user_id=$1 AND entry_id=$2
		`

		_, err := tx.Exec(query, entry.UserID, entry.ID)
		if err != nil {
			return fmt.Errorf(`store: unable to delete old enclosures: %v`, err)
		}
		return nil
	}

	sqlValues := make([]string, 0, len(entry.Enclosures))
	for _, enclosure := range entry.Enclosures {
		sqlValues = append(sqlValues, strings.TrimSpace(enclosure.URL))

		if err := s.createEnclosure(tx, enclosure); err != nil {
			return err
		}
	}

	query := `
		DELETE FROM
			enclosures
		WHERE
			user_id=$1 AND entry_id=$2 AND url <> ALL($3)
	`

	_, err := tx.Exec(query, entry.UserID, entry.ID, pq.Array(sqlValues))
	if err != nil {
		return fmt.Errorf(`store: unable to delete old enclosures: %v`, err)
	}

	return nil
}

// UpdateEnclosure persists changes to the given enclosure.
func (s *Storage) UpdateEnclosure(enclosure *model.Enclosure) error {
	query := `
		UPDATE
			enclosures
		SET
			url=$1,
			size=$2,
			mime_type=$3,
			entry_id=$4,
			user_id=$5,
			media_progression=$6
		WHERE
			id=$7
	`
	_, err := s.db.Exec(query,
		enclosure.URL,
		enclosure.Size,
		enclosure.MimeType,
		enclosure.EntryID,
		enclosure.UserID,
		enclosure.MediaProgression,
		enclosure.ID,
	)

	if err != nil {
		return fmt.Errorf(`store: unable to update enclosure #%d : %v`, enclosure.ID, err)
	}

	return nil
}
