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

package opml // import "miniflux.app/v2/internal/reader/opml"

import (
	"fmt"
	"io"

	"miniflux.app/v2/internal/model"
	"miniflux.app/v2/internal/storage"
	"miniflux.app/v2/internal/validator"
)

// Handler handles the logic for OPML import/export.
type Handler struct {
	store *storage.Storage
}

// Export exports user feeds to OPML.
func (h *Handler) Export(userID int64) (string, error) {
	feeds, err := h.store.Feeds(userID)
	if err != nil {
		return "", err
	}

	subscriptions := make([]subcription, 0, len(feeds))
	for _, feed := range feeds {
		subscriptions = append(subscriptions, subcription{
			Title:        feed.Title,
			FeedURL:      feed.FeedURL,
			SiteURL:      feed.SiteURL,
			Description:  feed.Description,
			CategoryName: feed.Category.Title,

			ScraperRules:                feed.ScraperRules,
			RewriteRules:                feed.RewriteRules,
			UrlRewriteRules:             feed.UrlRewriteRules,
			BlocklistRules:              feed.BlocklistRules,
			KeeplistRules:               feed.KeeplistRules,
			BlockFilterEntryRules:       feed.BlockFilterEntryRules,
			KeepFilterEntryRules:        feed.KeepFilterEntryRules,
			UserAgent:                   feed.UserAgent,
			Crawler:                     feed.Crawler,
			IgnoreHTTPCache:             feed.IgnoreHTTPCache,
			FetchViaProxy:               feed.FetchViaProxy,
			Disabled:                    feed.Disabled,
			NoMediaPlayer:               feed.NoMediaPlayer,
			HideGlobally:                feed.HideGlobally,
			AllowSelfSignedCertificates: feed.AllowSelfSignedCertificates,
			DisableHTTP2:                feed.DisableHTTP2,
			IgnoreEntryUpdates:          feed.IgnoreEntryUpdates,
		})
	}

	return serialize(subscriptions), nil
}

// Import parses and create feeds from an OPML import.
func (h *Handler) Import(userID int64, data io.Reader) error {
	subscriptions, err := parse(data)
	if err != nil {
		return err
	}

	for _, subscription := range subscriptions {
		if h.store.FeedURLExists(userID, subscription.FeedURL) {
			continue
		}

		category, err := h.resolveCategory(userID, subscription.CategoryName)
		if err != nil {
			return err
		}

		if validationErr := validateSubscription(userID, category.ID, h.store, subscription); validationErr != nil {
			return fmt.Errorf(`opml: invalid feed settings for %q: %w`, subscription.FeedURL, validationErr)
		}

		feed := &model.Feed{
			UserID:      userID,
			Title:       subscription.Title,
			FeedURL:     subscription.FeedURL,
			SiteURL:     subscription.SiteURL,
			Description: subscription.Description,
			Category:    category,
		}
		applySubscriptionSettings(feed, subscription)
		if err := h.store.CreateFeed(feed); err != nil {
			return fmt.Errorf(`opml: unable to create this feed: %q`, subscription.FeedURL)
		}
	}

	return nil
}

func (h *Handler) resolveCategory(userID int64, categoryName string) (*model.Category, error) {
	if categoryName == "" {
		category, err := h.store.FirstCategory(userID)
		if err != nil {
			return nil, fmt.Errorf("opml: unable to find first category: %w", err)
		}
		return category, nil
	}

	category, err := h.store.CategoryByTitle(userID, categoryName)
	if err != nil {
		return nil, fmt.Errorf("opml: unable to search category by title: %w", err)
	}

	if category == nil {
		category, err = h.store.CreateCategory(userID, &model.CategoryCreationRequest{Title: categoryName})
		if err != nil {
			return nil, fmt.Errorf(`opml: unable to create this category: %q`, categoryName)
		}
	}

	return category, nil
}

func applySubscriptionSettings(feed *model.Feed, s subcription) {
	feed.ScraperRules = s.ScraperRules
	feed.RewriteRules = s.RewriteRules
	feed.UrlRewriteRules = s.UrlRewriteRules
	feed.BlocklistRules = s.BlocklistRules
	feed.KeeplistRules = s.KeeplistRules
	feed.BlockFilterEntryRules = s.BlockFilterEntryRules
	feed.KeepFilterEntryRules = s.KeepFilterEntryRules
	feed.UserAgent = s.UserAgent
	feed.Crawler = s.Crawler
	feed.IgnoreHTTPCache = s.IgnoreHTTPCache
	feed.FetchViaProxy = s.FetchViaProxy
	feed.Disabled = s.Disabled
	feed.NoMediaPlayer = s.NoMediaPlayer
	feed.HideGlobally = s.HideGlobally
	feed.AllowSelfSignedCertificates = s.AllowSelfSignedCertificates
	feed.DisableHTTP2 = s.DisableHTTP2
	feed.IgnoreEntryUpdates = s.IgnoreEntryUpdates
}

func validateSubscription(userID, categoryID int64, store *storage.Storage, s subcription) error {
	feedCreationRequest := &model.FeedCreationRequest{
		FeedURL:                     s.FeedURL,
		CategoryID:                  categoryID,
		UserAgent:                   s.UserAgent,
		Crawler:                     s.Crawler,
		IgnoreEntryUpdates:          s.IgnoreEntryUpdates,
		Disabled:                    s.Disabled,
		NoMediaPlayer:               s.NoMediaPlayer,
		IgnoreHTTPCache:             s.IgnoreHTTPCache,
		AllowSelfSignedCertificates: s.AllowSelfSignedCertificates,
		FetchViaProxy:               s.FetchViaProxy,
		HideGlobally:                s.HideGlobally,
		DisableHTTP2:                s.DisableHTTP2,
		ScraperRules:                s.ScraperRules,
		RewriteRules:                s.RewriteRules,
		BlocklistRules:              s.BlocklistRules,
		KeeplistRules:               s.KeeplistRules,
		BlockFilterEntryRules:       s.BlockFilterEntryRules,
		KeepFilterEntryRules:        s.KeepFilterEntryRules,
		UrlRewriteRules:             s.UrlRewriteRules,
	}

	if validationErr := validator.ValidateFeedCreation(store, userID, feedCreationRequest); validationErr != nil {
		return validationErr.Error()
	}

	return nil
}

// NewHandler creates a new handler for OPML files.
func NewHandler(store *storage.Storage) *Handler {
	return &Handler{store: store}
}
