package readlist

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/go-shiori/go-readability"
	"github.com/google/uuid"
	"gorm.io/gorm"
	"jaytaylor.com/html2text"
)

// ImportStatus represents the status of a Shiori import operation
type ImportStatus struct {
	ID             string     `json:"id"`
	TotalBookmarks int        `json:"totalBookmarks"`
	ImportedCount  int        `json:"importedCount"`
	FailedCount    int        `json:"failedCount"`
	InProgress     bool       `json:"inProgress"`
	StartedAt      time.Time  `json:"startedAt"`
	CompletedAt    *time.Time `json:"completedAt"`
	Error          string     `json:"error,omitempty"`
}

// Service handles read later operations
type Service struct {
	db           *gorm.DB
	imports      map[string]*ImportStatus
	importsMutex sync.RWMutex
	rateLimiter  *time.Ticker // For rate limiting API calls
}

// NewService creates a new read later service
func NewService(db *gorm.DB) *Service {
	return &Service{
		db:           db,
		imports:      make(map[string]*ImportStatus),
		importsMutex: sync.RWMutex{},
		rateLimiter:  time.NewTicker(500 * time.Millisecond), // 500ms delay between requests
	}
}

// Create adds a new URL to read later
func (s *Service) Create(url string) (*ReadLaterItem, error) {
	item := &ReadLaterItem{
		ID:        uuid.New().String(),
		URL:       url,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	// Parse URL and extract content
	if err := item.ParseURL(); err != nil {
		return nil, fmt.Errorf("failed to parse URL: %w", err)
	}

	if err := s.db.Create(item).Error; err != nil {
		return nil, fmt.Errorf("failed to create read later item: %w", err)
	}

	return item, nil
}

// Get retrieves a read later item by ID
func (s *Service) Get(id string) (*ReadLaterItem, error) {
	var item ReadLaterItem
	if err := s.db.First(&item, "id = ?", id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get read later item: %w", err)
	}
	return &item, nil
}

// List retrieves all read later items
func (s *Service) List(includeArchived bool) ([]ReadLaterItem, error) {
	var items []ReadLaterItem
	query := s.db.Order("created_at desc")

	if !includeArchived {
		query = query.Where("archived_at IS NULL")
	}

	if err := query.Find(&items).Error; err != nil {
		return nil, fmt.Errorf("failed to list read later items: %w", err)
	}
	return items, nil
}

// MarkRead marks an item as read
func (s *Service) MarkRead(id string) error {
	now := time.Now()
	if err := s.db.Model(&ReadLaterItem{}).
		Where("id = ?", id).
		Updates(map[string]interface{}{
			"read_at":    &now,
			"updated_at": now,
		}).Error; err != nil {
		return fmt.Errorf("failed to mark item as read: %w", err)
	}
	return nil
}

// Archive marks an item as archived
func (s *Service) Archive(id string) error {
	now := time.Now()
	if err := s.db.Model(&ReadLaterItem{}).
		Where("id = ?", id).
		Updates(map[string]interface{}{
			"archived_at": &now,
			"updated_at":  now,
		}).Error; err != nil {
		return fmt.Errorf("failed to archive item: %w", err)
	}
	return nil
}

// Unarchive removes the archived status from an item
func (s *Service) Unarchive(id string) error {
	now := time.Now()
	if err := s.db.Model(&ReadLaterItem{}).
		Where("id = ?", id).
		Updates(map[string]interface{}{
			"archived_at": nil,
			"updated_at":  now,
		}).Error; err != nil {
		return fmt.Errorf("failed to unarchive item: %w", err)
	}
	return nil
}

// Delete removes a read later item
func (s *Service) Delete(id string) error {
	if err := s.db.Delete(&ReadLaterItem{}, "id = ?", id).Error; err != nil {
		return fmt.Errorf("failed to delete read later item: %w", err)
	}
	return nil
}

// Reset deletes all read later items (for testing)
func (s *Service) Reset() error {
	if err := s.db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&ReadLaterItem{}).Error; err != nil {
		return fmt.Errorf("failed to reset read later items: %w", err)
	}
	return nil
}

// ShioriCredentials contains the credentials for connecting to a Shiori instance
type ShioriCredentials struct {
	URL      string `json:"url"`
	Username string `json:"username"`
	Password string `json:"password"`
}

// ShioriBookmark represents a bookmark in Shiori
type ShioriBookmark struct {
	ID            int             `json:"id"`
	URL           string          `json:"url"`
	Title         string          `json:"title"`
	Excerpt       string          `json:"excerpt"`
	Author        string          `json:"author"`
	Public        int             `json:"public"`
	CreatedAt     string          `json:"createdAt"`
	ModifiedAt    string          `json:"modifiedAt"`
	Modified      time.Time       `json:"modified,omitempty"`
	Content       string          `json:"content,omitempty"`
	HTML          string          `json:"html,omitempty"`
	ImageURL      string          `json:"imageURL,omitempty"`
	HasContent    bool            `json:"hasContent"`
	HasImage      bool            `json:"hasImage"`
	HasArchive    bool            `json:"hasArchive,omitempty"`
	HasEbook      bool            `json:"hasEbook,omitempty"`
	CreateArchive bool            `json:"create_archive,omitempty"`
	CreateEbook   bool            `json:"create_ebook,omitempty"`
	Tags          json.RawMessage `json:"tags,omitempty"`
}

// ShioriLoginResponse represents the response from Shiori login API
type ShioriLoginResponse struct {
	OK      bool `json:"ok"`
	Message struct {
		Token   string `json:"token"`   // JWT token for bearer auth
		Session string `json:"session"` // Session ID
		Expires int64  `json:"expires"`
	} `json:"message"`
}

// ShioriBookmarksResponse represents the response from Shiori bookmarks API
type ShioriBookmarksResponse struct {
	Bookmarks []ShioriBookmark `json:"bookmarks"`
	MaxPage   int              `json:"maxPage"`
	Count     int              `json:"count"`
}

// ImportFromShiori imports bookmarks from a Shiori instance
// Returns the import ID which can be used to check the status
func (s *Service) ImportFromShiori(creds ShioriCredentials) (string, error) {
	// For debugging
	fmt.Printf("[DEBUG] Starting import from Shiori URL: %s\n", creds.URL)

	// Create a new import status
	importID := s.CreateImportStatus()
	fmt.Printf("[DEBUG] Created import status with ID: %s\n", importID)

	// Start the import process in a goroutine
	go s.runShioriImport(importID, creds)

	// Return the import ID so the caller can check the status
	return importID, nil
}

// runShioriImport performs the actual import process in the background
func (s *Service) runShioriImport(importID string, creds ShioriCredentials) {
	fmt.Printf("[DEBUG] Starting runShioriImport for ID: %s\n", importID)

	// Define a helper function to mark import as failed
	markFailed := func(err error) {
		fmt.Printf("[DEBUG] Import failed: %v\n", err)
		s.UpdateImportStatus(importID, func(status *ImportStatus) {
			status.InProgress = false
			status.Error = err.Error()
		})
	}

	// Login to Shiori
	fmt.Printf("[DEBUG] Attempting to login to Shiori at %s\n", creds.URL)
	token, err := s.loginToShiori(creds)
	if err != nil {
		fmt.Printf("[DEBUG] Login failed: %v\n", err)
		markFailed(fmt.Errorf("failed to login to Shiori: %v", err))
		return
	}
	fmt.Printf("[DEBUG] Login successful, token: %s...\n", token[:min(len(token), 10)])

	// Fetch all bookmarks from Shiori with pagination
	fmt.Printf("[DEBUG] Fetching bookmarks from Shiori\n")
	allBookmarks, err := s.fetchShioriBookmarks(creds.URL, token)
	if err != nil {
		fmt.Printf("[DEBUG] Fetching bookmarks failed: %v\n", err)
		markFailed(fmt.Errorf("failed to fetch bookmarks: %v", err))
		return
	}
	fmt.Printf("[DEBUG] Fetched %d bookmarks\n", len(allBookmarks))

	// Setup counters
	importedCount := 0
	failedCount := 0

	// Update status with total count
	s.UpdateImportStatus(importID, func(status *ImportStatus) {
		status.TotalBookmarks = len(allBookmarks)
	})
	fmt.Printf("[DEBUG] Updated import status with total count: %d\n", len(allBookmarks))

	// Process each bookmark
	for i, bookmark := range allBookmarks {
		fmt.Printf("[DEBUG] Processing bookmark %d/%d: %s\n", i+1, len(allBookmarks), bookmark.URL)
		<-s.rateLimiter.C // Apply rate limiting for processing each bookmark

		// Create a ReadLaterItem from the bookmark
		item := ReadLaterItem{
			ID:          uuid.New().String(),
			URL:         bookmark.URL,
			Title:       bookmark.Title,
			Image:       bookmark.ImageURL,
			Description: bookmark.Excerpt,
			Status:      "unread",
			Content:     bookmark.Content,
			CreatedAt:   time.Now(),
			UpdatedAt:   time.Now(),
		}

		// If content is empty, try to fetch it
		if item.Content == "" {
			fmt.Printf("[DEBUG] Content empty, fetching from URL: %s\n", bookmark.URL)
			article, err := readability.FromURL(bookmark.URL, 30*time.Second)
			if err == nil && article.Content != "" {
				item.Content = article.Content

				// If description/excerpt is empty, use the beginning of the content
				if item.Description == "" {
					plainText, err := s.ConvertHTMLToText(article.Content)
					if err == nil && plainText != "" {
						// Use the first 150 characters as description
						if len(plainText) > 150 {
							item.Description = plainText[:150] + "..."
						} else {
							item.Description = plainText
						}
					}
				}

				// If title is empty, use article title
				if item.Title == "" && article.Title != "" {
					item.Title = article.Title
				}

				// If image is empty, use article image
				if item.Image == "" && article.Image != "" {
					item.Image = article.Image
				}
			} else if err != nil {
				fmt.Printf("[DEBUG] Error fetching article content: %v\n", err)
			}
		} else if item.Description == "" { // Convert any existing HTML content to plain text for the description if needed
			plainText, err := s.ConvertHTMLToText(item.Content)
			if err == nil && plainText != "" {
				// Use the first 150 characters as description
				if len(plainText) > 150 {
					item.Description = plainText[:150] + "..."
				} else {
					item.Description = plainText
				}
			} else if err != nil {
				fmt.Printf("[DEBUG] Error converting HTML to text: %v\n", err)
			}
		}

		// Ensure we have at least a basic title if it's still empty
		if item.Title == "" {
			item.Title = bookmark.URL
		}

		// Save the item to the database
		fmt.Printf("[DEBUG] Saving item to database: %s\n", item.URL)
		err = s.db.Create(&item).Error
		if err != nil {
			fmt.Printf("[DEBUG] Error saving item: %v\n", err)
			failedCount++
		} else {
			importedCount++
		}

		// Update import status
		s.UpdateImportStatus(importID, func(status *ImportStatus) {
			status.ImportedCount = importedCount
			status.FailedCount = failedCount
		})
	}

	// Mark import as complete
	fmt.Printf("[DEBUG] Import complete. Imported: %d, Failed: %d\n", importedCount, failedCount)
	s.UpdateImportStatus(importID, func(status *ImportStatus) {
		status.InProgress = false
		completedAt := time.Now()
		status.CompletedAt = &completedAt
	})
}

// loginToShiori logs in to a Shiori instance and returns the session token
func (s *Service) loginToShiori(creds ShioriCredentials) (string, error) {
	// Apply rate limiting
	<-s.rateLimiter.C

	fmt.Printf("[DEBUG] loginToShiori: Starting login to %s\n", creds.URL)

	// Construct login URL
	loginURL := fmt.Sprintf("%s/api/v1/auth/login", strings.TrimSuffix(creds.URL, "/"))
	fmt.Printf("[DEBUG] loginToShiori: Login URL: %s\n", loginURL)

	// Prepare login data
	loginData := map[string]string{
		"username": creds.Username,
		"password": creds.Password,
	}

	// Convert login data to JSON
	jsonData, err := json.Marshal(loginData)
	if err != nil {
		fmt.Printf("[DEBUG] loginToShiori: Error marshaling login data: %v\n", err)
		return "", fmt.Errorf("failed to marshal login data: %v", err)
	}

	// Create request
	req, err := http.NewRequest("POST", loginURL, bytes.NewBuffer(jsonData))
	if err != nil {
		fmt.Printf("[DEBUG] loginToShiori: Error creating request: %v\n", err)
		return "", fmt.Errorf("failed to create login request: %v", err)
	}

	// Set headers
	req.Header.Set("Content-Type", "application/json")

	// Send request
	fmt.Printf("[DEBUG] loginToShiori: Sending login request\n")
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("[DEBUG] loginToShiori: Error sending request: %v\n", err)
		return "", fmt.Errorf("failed to send login request: %v", err)
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			fmt.Printf("[DEBUG] fetchShioriBookmarks: Error closing response body: %v\n", err)
		}
	}()

	// Check response status
	fmt.Printf("[DEBUG] loginToShiori: Response status: %s\n", resp.Status)
	if resp.StatusCode != http.StatusOK {
		// Read the error response body
		errBody, _ := io.ReadAll(resp.Body)
		fmt.Printf("[DEBUG] loginToShiori: Error response body: %s\n", string(errBody))
		return "", fmt.Errorf("login failed with status code: %d", resp.StatusCode)
	}

	// Read response body
	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("[DEBUG] loginToShiori: Error reading response body: %v\n", err)
		return "", fmt.Errorf("failed to read login response: %v", err)
	}

	// Print response body for debugging
	fmt.Printf("[DEBUG] loginToShiori: Response body: %s\n", string(bodyBytes))

	// We need to re-create the reader since we consumed it
	resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

	// Parse response
	var loginResp ShioriLoginResponse
	if err := json.NewDecoder(resp.Body).Decode(&loginResp); err != nil {
		fmt.Printf("[DEBUG] loginToShiori: Error parsing response: %v\n", err)
		return "", fmt.Errorf("failed to parse login response: %v", err)
	}

	// Extract token
	token := loginResp.Message.Token
	fmt.Printf("[DEBUG] loginToShiori: Successfully logged in, token length: %d\n", len(token))

	return token, nil
}

// fetchShioriBookmarks retrieves bookmarks from a Shiori instance using the session token
func (s *Service) fetchShioriBookmarks(baseURL, session string) ([]ShioriBookmark, error) {
	fmt.Printf("[DEBUG] fetchShioriBookmarks: Starting fetching bookmarks from %s\n", baseURL)
	// Apply rate limiting
	<-s.rateLimiter.C

	// Ensure baseURL doesn't have a trailing slash
	baseURL = strings.TrimSuffix(baseURL, "/")

	// Initialize results
	allBookmarks := []ShioriBookmark{}

	// Start with page 1
	page := 1
	maxPage := 1 // Will be updated from the first response

	for page <= maxPage {
		fmt.Printf("[DEBUG] fetchShioriBookmarks: Fetching page %d of %d\n", page, maxPage)

		// Construct URL for the current page - fixing the API endpoint path
		url := fmt.Sprintf("%s/api/bookmarks?page=%d", baseURL, page)
		fmt.Printf("[DEBUG] fetchShioriBookmarks: Request URL for page %d: %s\n", page, url)

		// Create request
		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			fmt.Printf("[DEBUG] fetchShioriBookmarks: Error creating request for page %d: %v\n", page, err)
			return nil, fmt.Errorf("failed to create request: %v", err)
		}

		// Set headers
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", session))

		// Send request
		fmt.Printf("[DEBUG] fetchShioriBookmarks: Sending request for page %d\n", page)
		client := &http.Client{}
		resp, err := client.Do(req)
		if err != nil {
			fmt.Printf("[DEBUG] fetchShioriBookmarks: Error sending request for page %d: %v\n", page, err)
			return nil, fmt.Errorf("failed to send request: %v", err)
		}
		defer func() {
			if err := resp.Body.Close(); err != nil {
				fmt.Printf("[DEBUG] fetchShioriBookmarks: Error closing response body: %v\n", err)
			}
		}()

		// Check response
		fmt.Printf("[DEBUG] fetchShioriBookmarks: Response status for page %d: %s\n", page, resp.Status)
		if resp.StatusCode != http.StatusOK {
			// Read the error response body
			errBody, _ := io.ReadAll(resp.Body)
			fmt.Printf("[DEBUG] fetchShioriBookmarks: Error response body for page %d: %s\n", page, string(errBody))
			return nil, fmt.Errorf("request failed with status code: %d", resp.StatusCode)
		}

		// Read response body
		bodyBytes, err := io.ReadAll(resp.Body)
		if err != nil {
			fmt.Printf("[DEBUG] fetchShioriBookmarks: Error reading response body for page %d: %v\n", page, err)
			return nil, fmt.Errorf("failed to read response: %v", err)
		}

		// Print the first 200 characters of response for debugging
		previewText := string(bodyBytes)
		if len(previewText) > 200 {
			previewText = previewText[:200] + "..."
		}
		fmt.Printf("[DEBUG] fetchShioriBookmarks: Response body preview for page %d: %s\n", page, previewText)

		// We need to re-create the reader since we consumed it
		resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

		// Parse response
		var bookmarksResp ShioriBookmarksResponse
		if err := json.NewDecoder(resp.Body).Decode(&bookmarksResp); err != nil {
			fmt.Printf("[DEBUG] fetchShioriBookmarks: Error parsing response for page %d: %v\n", page, err)
			return nil, fmt.Errorf("failed to parse response: %v", err)
		}

		// Update maxPage from the response (only on first page)
		if page == 1 {
			maxPage = bookmarksResp.MaxPage
			fmt.Printf("[DEBUG] fetchShioriBookmarks: Updated max page to %d\n", maxPage)
		}

		// Add bookmarks from this page to results
		allBookmarks = append(allBookmarks, bookmarksResp.Bookmarks...)
		fmt.Printf("[DEBUG] fetchShioriBookmarks: Added %d bookmarks from page %d, total so far: %d\n",
			len(bookmarksResp.Bookmarks), page, len(allBookmarks))

		// Move to next page
		page++

		// Apply rate limiting before next request (if there is one)
		if page <= maxPage {
			<-s.rateLimiter.C
		}
	}

	fmt.Printf("[DEBUG] fetchShioriBookmarks: Completed, fetched %d bookmarks total\n", len(allBookmarks))
	return allBookmarks, nil
}

// CreateImportStatus initializes a new import status and returns its ID
func (s *Service) CreateImportStatus() string {
	id := uuid.New().String()
	status := &ImportStatus{
		ID:            id,
		InProgress:    true,
		StartedAt:     time.Now(),
		ImportedCount: 0,
		FailedCount:   0,
	}

	s.importsMutex.Lock()
	s.imports[id] = status
	s.importsMutex.Unlock()

	return id
}

// UpdateImportStatus updates the status of an import operation
func (s *Service) UpdateImportStatus(id string, update func(*ImportStatus)) {
	s.importsMutex.Lock()
	defer s.importsMutex.Unlock()

	if status, exists := s.imports[id]; exists {
		update(status)
	}
}

// GetImportStatus retrieves the status of an import operation
func (s *Service) GetImportStatus(id string) (*ImportStatus, bool) {
	s.importsMutex.RLock()
	defer s.importsMutex.RUnlock()

	status, exists := s.imports[id]
	return status, exists
}

// CleanupOldImports removes completed import statuses older than a day
func (s *Service) CleanupOldImports() {
	s.importsMutex.Lock()
	defer s.importsMutex.Unlock()

	cutoff := time.Now().Add(-24 * time.Hour)

	for id, status := range s.imports {
		if !status.InProgress && status.CompletedAt != nil && status.CompletedAt.Before(cutoff) {
			delete(s.imports, id)
		}
	}
}

// ConvertHTMLToText converts HTML content to plain text
// This is useful when we need to extract text from HTML for searching or displaying in non-HTML contexts
func (s *Service) ConvertHTMLToText(htmlContent string) (string, error) {
	if htmlContent == "" {
		return "", nil
	}

	// Convert HTML to plain text
	text, err := html2text.FromString(htmlContent, html2text.Options{
		PrettyTables: true,
		OmitLinks:    false,
	})

	if err != nil {
		return "", err
	}

	return text, nil
}

// Add helper function for safe string slicing
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}