package feeds

import (
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"strings"
	"time"

	"github.com/go-co-op/gocron"
	"github.com/go-shiori/go-readability"
	"github.com/mmcdole/gofeed"
	"gorm.io/gorm"
)

// Service handles feed operations
type Service struct {
	db     *gorm.DB
	parser *gofeed.Parser
	cron   *gocron.Scheduler
}

// NewService creates a new feed service
func NewService(db *gorm.DB) *Service {
	s := &Service{
		db:     db,
		parser: gofeed.NewParser(),
		cron:   gocron.NewScheduler(time.UTC),
	}

	// Start the scheduler
	_, err := s.cron.Every(1).Hour().Do(s.RefreshAllFeeds)
	if err != nil {
		log.Printf("Error scheduling feed refresh: %v", err)
	}
	s.cron.StartAsync()

	return s
}

// AddFeed adds a new feed
func (s *Service) AddFeed(url string) (*Feed, error) {
	// Check if feed already exists
	var existingFeed Feed
	if result := s.db.Where("url = ?", url).First(&existingFeed); result.Error == nil {
		return &existingFeed, nil
	}

	// Fetch and parse the feed
	feed, err := s.parser.ParseURL(url)
	if err != nil {
		return nil, fmt.Errorf("failed to parse feed: %w", err)
	}

	// Create new feed
	newFeed := Feed{
		Title:       feed.Title,
		URL:         url,
		Description: feed.Description,
		SiteURL:     feed.Link,
		ImageURL:    s.extractFeedImage(feed),
		LastFetched: time.Now(),
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}

	// Save feed to database
	if err := s.db.Create(&newFeed).Error; err != nil {
		return nil, fmt.Errorf("failed to save feed: %w", err)
	}

	// Process feed entries
	s.processFeedEntries(&newFeed, feed)

	return &newFeed, nil
}

// ImportOPML imports feeds from an OPML file
func (s *Service) ImportOPML(opmlContent io.Reader) ([]Feed, error) {
	// Read the content into a buffer so we can inspect it if there's an error
	content, err := io.ReadAll(opmlContent)
	if err != nil {
		return nil, fmt.Errorf("failed to read OPML content: %w", err)
	}

	// Check if the content looks like XML
	trimmedContent := strings.TrimSpace(string(content))
	if !strings.HasPrefix(trimmedContent, "<?xml") && !strings.HasPrefix(trimmedContent, "<opml") {
		// Try to provide a helpful error message
		if strings.HasPrefix(trimmedContent, "<!DOCTYPE") || strings.HasPrefix(trimmedContent, "<html") {
			return nil, fmt.Errorf("received HTML instead of OPML XML. Please check that the file is a valid OPML file")
		}
		return nil, fmt.Errorf("content does not appear to be valid OPML XML")
	}

	var opml OPML
	if err := xml.Unmarshal(content, &opml); err != nil {
		return nil, fmt.Errorf("failed to parse OPML: %w", err)
	}

	var feeds []Feed
	for _, outline := range opml.Body.Outlines {
		if outline.XMLURL != "" {
			feed, err := s.AddFeed(outline.XMLURL)
			if err != nil {
				continue // Skip feeds that fail to parse
			}
			feeds = append(feeds, *feed)
		}

		// Process nested outlines
		for _, nestedOutline := range outline.Outlines {
			if nestedOutline.XMLURL != "" {
				feed, err := s.AddFeed(nestedOutline.XMLURL)
				if err != nil {
					continue
				}
				feeds = append(feeds, *feed)
			}
		}
	}

	return feeds, nil
}

// GetFeeds returns all feeds
func (s *Service) GetFeeds() ([]Feed, error) {
	var feeds []Feed
	if err := s.db.Order("title").Find(&feeds).Error; err != nil {
		return nil, err
	}
	return feeds, nil
}

// GetFeed returns a feed by ID
func (s *Service) GetFeed(id string) (*Feed, error) {
	var feed Feed
	if err := s.db.First(&feed, "id = ?", id).Error; err != nil {
		return nil, err
	}
	return &feed, nil
}

// DeleteFeed deletes a feed by ID
func (s *Service) DeleteFeed(id string) error {
	// Delete associated entries first
	if err := s.db.Delete(&Entry{}, "feed_id = ?", id).Error; err != nil {
		return err
	}

	// Delete the feed
	if err := s.db.Delete(&Feed{}, "id = ?", id).Error; err != nil {
		return err
	}

	return nil
}

// RefreshFeed refreshes a single feed
func (s *Service) RefreshFeed(id string) error {
	var feed Feed
	if err := s.db.First(&feed, "id = ?", id).Error; err != nil {
		return err
	}

	// Fetch and parse the feed
	parsedFeed, err := s.parser.ParseURL(feed.URL)
	if err != nil {
		return fmt.Errorf("failed to parse feed: %w", err)
	}

	// Update feed metadata
	feed.Title = parsedFeed.Title
	feed.Description = parsedFeed.Description
	feed.SiteURL = parsedFeed.Link
	feed.ImageURL = s.extractFeedImage(parsedFeed)
	feed.LastFetched = time.Now()
	feed.UpdatedAt = time.Now()

	// Save updated feed
	if err := s.db.Save(&feed).Error; err != nil {
		return fmt.Errorf("failed to update feed: %w", err)
	}

	// Process feed entries
	s.processFeedEntries(&feed, parsedFeed)

	return nil
}

// RefreshAllFeeds refreshes all feeds
func (s *Service) RefreshAllFeeds() error {
	var feeds []Feed
	if err := s.db.Find(&feeds).Error; err != nil {
		return err
	}

	for _, feed := range feeds {
		// Ignore errors for individual feeds to continue with others
		_ = s.RefreshFeed(feed.ID)
	}

	return nil
}

// GetEntries returns entries for all feeds or a specific feed
func (s *Service) GetEntries(feedID string, unreadOnly bool) ([]Entry, error) {
	query := s.db.Order("published desc")

	if feedID != "" {
		query = query.Where("feed_id = ?", feedID)
	}

	if unreadOnly {
		query = query.Where("read_at IS NULL")
	}

	var entries []Entry
	if err := query.Find(&entries).Error; err != nil {
		return nil, err
	}

	return entries, nil
}

// GetEntry returns an entry by ID
func (s *Service) GetEntry(id string) (*Entry, error) {
	var entry Entry
	if err := s.db.First(&entry, "id = ?", id).Error; err != nil {
		return nil, err
	}
	return &entry, nil
}

// MarkEntryAsRead marks an entry as read
func (s *Service) MarkEntryAsRead(id string) error {
	return s.db.Model(&Entry{}).Where("id = ?", id).Update("read_at", time.Now()).Error
}

// MarkAllEntriesAsRead marks all entries as read, optionally filtered by feed ID
func (s *Service) MarkAllEntriesAsRead(feedID string) error {
	query := s.db.Model(&Entry{}).Where("read_at IS NULL")

	if feedID != "" {
		query = query.Where("feed_id = ?", feedID)
	}

	return query.Update("read_at", time.Now()).Error
}

// FetchFullContent fetches and parses the full content of an entry
func (s *Service) FetchFullContent(id string) error {
	var entry Entry
	if err := s.db.First(&entry, "id = ?", id).Error; err != nil {
		return err
	}

	// Skip if already has full content
	if entry.FullContent != "" {
		return nil
	}

	// Fetch and parse the article
	article, err := readability.FromURL(entry.URL, 30*time.Second)
	if err != nil {
		return fmt.Errorf("failed to fetch article: %w", err)
	}

	// Update entry with full content
	entry.FullContent = article.Content
	entry.UpdatedAt = time.Now()

	return s.db.Save(&entry).Error
}

// Helper function to process feed entries
func (s *Service) processFeedEntries(feed *Feed, parsedFeed *gofeed.Feed) {
	for _, item := range parsedFeed.Items {
		// Skip items without links
		if item.Link == "" {
			continue
		}

		// Check if entry already exists
		var existingEntry Entry
		result := s.db.Where("url = ?", item.Link).First(&existingEntry)

		if result.Error == nil {
			// Update existing entry if needed
			if item.UpdatedParsed != nil && existingEntry.Updated.Before(*item.UpdatedParsed) {
				existingEntry.Title = item.Title
				existingEntry.Summary = item.Description
				existingEntry.Content = item.Content
				existingEntry.Updated = *item.UpdatedParsed
				existingEntry.UpdatedAt = time.Now()
				s.db.Save(&existingEntry)
			}
			continue
		}

		// Create new entry
		published := time.Now()
		if item.PublishedParsed != nil {
			published = *item.PublishedParsed
		}

		updated := published
		if item.UpdatedParsed != nil {
			updated = *item.UpdatedParsed
		}

		author := ""
		if item.Author != nil {
			author = item.Author.Name
		}

		newEntry := Entry{
			FeedID:    feed.ID,
			Title:     item.Title,
			URL:       item.Link,
			Content:   item.Content,
			Summary:   item.Description,
			Author:    author,
			Published: published,
			Updated:   updated,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		}

		s.db.Create(&newEntry)
	}
}

// Helper function to extract feed image
func (s *Service) extractFeedImage(feed *gofeed.Feed) string {
	if feed.Image != nil && feed.Image.URL != "" {
		return feed.Image.URL
	}
	return ""
}

// OPML represents an OPML file structure
type OPML struct {
	XMLName xml.Name `xml:"opml"`
	Version string   `xml:"version,attr"`
	Head    struct {
		Title string `xml:"title"`
	} `xml:"head"`
	Body struct {
		Outlines []Outline `xml:"outline"`
	} `xml:"body"`
}

// Outline represents an OPML outline element
type Outline struct {
	Text     string    `xml:"text,attr"`
	Title    string    `xml:"title,attr"`
	Type     string    `xml:"type,attr"`
	XMLURL   string    `xml:"xmlUrl,attr"`
	HTMLURL  string    `xml:"htmlUrl,attr"`
	Outlines []Outline `xml:"outline"`
}