package notes

import (
	"archive/zip"
	"fmt"
	"io"
	"path/filepath"
	"strings"
	"time"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

// Service handles note operations
type Service struct {
	db *gorm.DB
}

// NewService creates a new note service
func NewService(db *gorm.DB) *Service {
	return &Service{db: db}
}

// Create creates a new note
func (s *Service) Create(note *Note) error {
	note.ID = uuid.New().String()

	err := s.db.Transaction(func(tx *gorm.DB) error {
		// Create the note
		if err := tx.Create(note).Error; err != nil {
			return fmt.Errorf("failed to create note: %w", err)
		}

		// Update links in this note
		if err := note.UpdateLinks(tx); err != nil {
			return fmt.Errorf("failed to update note links: %w", err)
		}

		// Find and update notes that link to this note's title
		var notesToUpdate []Note
		if err := tx.Where("content LIKE ?", "%[["+note.Title+"]]%").Find(&notesToUpdate).Error; err != nil {
			return fmt.Errorf("failed to find notes linking to %q: %w", note.Title, err)
		}

		for _, n := range notesToUpdate {
			if err := n.UpdateLinks(tx); err != nil {
				return fmt.Errorf("failed to update links in note %q: %w", n.Title, err)
			}
		}

		return nil
	})

	if err != nil {
		return err
	}

	// Load the note with its relationships
	if err := s.db.Preload("LinksTo").First(note).Error; err != nil {
		return fmt.Errorf("failed to load note relationships: %w", err)
	}

	return nil
}

// Get retrieves a note by ID
func (s *Service) Get(id string) (*Note, error) {
	var note Note
	if err := s.db.
		Preload("LinksTo", func(db *gorm.DB) *gorm.DB {
			return db.Select("id", "title")
		}).
		Preload("LinkedBy", func(db *gorm.DB) *gorm.DB {
			return db.Select("id", "title")
		}).
		First(&note, "id = ?", id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get note: %w", err)
	}
	return &note, nil
}

// List retrieves all notes
func (s *Service) List() ([]Note, error) {
	var notes []Note
	if err := s.db.
		Preload("LinksTo", func(db *gorm.DB) *gorm.DB {
			return db.Select("id", "title")
		}).
		Preload("LinkedBy", func(db *gorm.DB) *gorm.DB {
			return db.Select("id", "title")
		}).
		Order("updated_at desc").
		Find(&notes).Error; err != nil {
		return nil, fmt.Errorf("failed to list notes: %w", err)
	}
	return notes, nil
}

// Update updates a note
func (s *Service) Update(id string, updates map[string]interface{}) error {
	return s.db.Transaction(func(tx *gorm.DB) error {
		// Update the note
		if err := tx.Model(&Note{}).Where("id = ?", id).Updates(updates).Error; err != nil {
			return fmt.Errorf("failed to update note: %w", err)
		}

		// Load the updated note for link processing
		var note Note
		if err := tx.First(&note, "id = ?", id).Error; err != nil {
			return fmt.Errorf("failed to load note: %w", err)
		}

		// Update links
		if err := note.UpdateLinks(tx); err != nil {
			return fmt.Errorf("failed to update note links: %w", err)
		}

		return nil
	})
}

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

// ImportObsidianVault imports notes from an Obsidian vault zip file
func (s *Service) ImportObsidianVault(zipReader *zip.Reader) (int, error) {
	// Map to store file paths and their content
	noteFiles := make(map[string]string)

	// First pass: extract all markdown files
	for _, file := range zipReader.File {
		// Skip directories and non-markdown files
		if file.FileInfo().IsDir() || !strings.HasSuffix(strings.ToLower(file.Name), ".md") {
			continue
		}

		// Open the file
		rc, err := file.Open()
		if err != nil {
			return 0, fmt.Errorf("failed to open file %s: %w", file.Name, err)
		}

		// Read the content
		content, err := io.ReadAll(rc)
		if err != nil {
			if err := rc.Close(); err != nil {
				return 0, fmt.Errorf("failed to close file %s: %w", file.Name, err)
			}
			return 0, fmt.Errorf("failed to read file %s: %w", file.Name, err)
		}
		if err := rc.Close(); err != nil {
			return 0, fmt.Errorf("failed to close file %s: %w", file.Name, err)
		}

		// Store the content
		noteFiles[file.Name] = string(content)
	}

	// Map to store created notes by their original filename
	createdNotes := make(map[string]*Note)

	// Second pass: create notes without links
	for filePath, content := range noteFiles {
		// Extract title from filename
		fileName := filepath.Base(filePath)
		title := strings.TrimSuffix(fileName, filepath.Ext(fileName))

		// Create note
		note := &Note{
			ID:        uuid.New().String(),
			Title:     title,
			Content:   content,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		}

		// Save note to database
		if err := s.db.Create(note).Error; err != nil {
			return len(createdNotes), fmt.Errorf("failed to create note %s: %w", title, err)
		}

		// Store created note
		createdNotes[filePath] = note
	}

	// Third pass: update links between notes
	for filePath, note := range createdNotes {
		if err := s.db.Transaction(func(tx *gorm.DB) error {
			// Load the note
			if err := tx.First(note, "id = ?", note.ID).Error; err != nil {
				return fmt.Errorf("failed to load note %s: %w", note.Title, err)
			}

			// Update links
			if err := note.UpdateLinks(tx); err != nil {
				return fmt.Errorf("failed to update links in note %s: %w", note.Title, err)
			}

			return nil
		}); err != nil {
			return len(createdNotes), fmt.Errorf("failed to update links for note %s: %w", filePath, err)
		}
	}

	return len(createdNotes), nil
}

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