package api

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	yaml "gopkg.in/yaml.v3"

	"github.com/mathieu/project-notes/internal/indexer"
)

// REST API Structures
// ===================

// NoteResponse représente une note dans les réponses API
type NoteResponse struct {
	Path         string                    `json:"path"`
	Title        string                    `json:"title"`
	Content      string                    `json:"content,omitempty"`      // Full content with front matter
	Body         string                    `json:"body,omitempty"`         // Body without front matter
	FrontMatter  *indexer.FullFrontMatter  `json:"frontMatter,omitempty"`
	LastModified string                    `json:"lastModified"`
	Size         int64                     `json:"size"`
}

// NoteMetadata représente les métadonnées d'une note (pour la liste)
type NoteMetadata struct {
	Path         string   `json:"path"`
	Title        string   `json:"title"`
	Tags         []string `json:"tags"`
	LastModified string   `json:"lastModified"`
	Date         string   `json:"date"`
	Size         int64    `json:"size"`
}

// ListNotesResponse représente la réponse de liste de notes
type ListNotesResponse struct {
	Notes []NoteMetadata `json:"notes"`
	Total int            `json:"total"`
}

// ErrorResponse représente une erreur API
type ErrorResponse struct {
	Error   string `json:"error"`
	Message string `json:"message"`
	Code    int    `json:"code"`
}

// NoteRequest représente une requête de création/modification de note
type NoteRequest struct {
	Content     string                   `json:"content,omitempty"`
	Body        string                   `json:"body,omitempty"`
	FrontMatter *indexer.FullFrontMatter `json:"frontMatter,omitempty"`
}

// REST API Handlers
// =================

// handleRESTNotes route les requêtes REST vers les bons handlers
func (h *Handler) handleRESTNotes(w http.ResponseWriter, r *http.Request) {
	// Extract path after /api/v1/notes/
	const prefix = "/api/v1/notes"
	path := strings.TrimPrefix(r.URL.Path, prefix)
	path = strings.TrimPrefix(path, "/")

	// Si pas de path spécifique, c'est une liste
	if path == "" {
		switch r.Method {
		case http.MethodGet:
			h.handleRESTListNotes(w, r)
		default:
			h.sendJSONError(w, "Method not allowed", http.StatusMethodNotAllowed)
		}
		return
	}

	// Validation du path
	cleanPath := filepath.Clean(path)
	if strings.HasPrefix(cleanPath, "..") || filepath.IsAbs(cleanPath) {
		h.sendJSONError(w, "Invalid path", http.StatusBadRequest)
		return
	}

	if filepath.Ext(cleanPath) != ".md" {
		h.sendJSONError(w, "Only .md files are supported", http.StatusBadRequest)
		return
	}

	// Router selon la méthode HTTP
	switch r.Method {
	case http.MethodGet:
		h.handleRESTGetNote(w, r, cleanPath)
	case http.MethodPut:
		h.handleRESTPutNote(w, r, cleanPath)
	case http.MethodDelete:
		h.handleRESTDeleteNote(w, r, cleanPath)
	default:
		h.sendJSONError(w, "Method not allowed. Supported: GET, PUT, DELETE", http.StatusMethodNotAllowed)
	}
}

// handleRESTListNotes liste toutes les notes avec leurs métadonnées
// GET /api/v1/notes
func (h *Handler) handleRESTListNotes(w http.ResponseWriter, r *http.Request) {
	notes := []NoteMetadata{}

	err := filepath.WalkDir(h.notesDir, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			return err
		}

		// Ignorer les dossiers et fichiers cachés
		if d.IsDir() || strings.HasPrefix(d.Name(), ".") {
			return nil
		}

		// Ne garder que les .md
		if filepath.Ext(path) != ".md" {
			return nil
		}

		relPath, err := filepath.Rel(h.notesDir, path)
		if err != nil {
			return err
		}

		// Lire les métadonnées
		info, err := d.Info()
		if err != nil {
			h.logger.Printf("Erreur lecture info fichier %s: %v", relPath, err)
			return nil // Continue malgré l'erreur
		}

		// Extraire le front matter
		fm, _, err := indexer.ExtractFrontMatterAndBody(path)
		if err != nil {
			h.logger.Printf("Erreur extraction front matter %s: %v", relPath, err)
			// Continuer avec des valeurs par défaut
			fm = indexer.FullFrontMatter{
				Title: strings.TrimSuffix(d.Name(), ".md"),
				Tags:  []string{},
			}
		}

		notes = append(notes, NoteMetadata{
			Path:         filepath.ToSlash(relPath),
			Title:        fm.Title,
			Tags:         fm.Tags,
			LastModified: fm.LastModified,
			Date:         fm.Date,
			Size:         info.Size(),
		})

		return nil
	})

	if err != nil {
		h.logger.Printf("Erreur lors du listing des notes: %v", err)
		h.sendJSONError(w, "Failed to list notes", http.StatusInternalServerError)
		return
	}

	response := ListNotesResponse{
		Notes: notes,
		Total: len(notes),
	}

	h.sendJSON(w, response, http.StatusOK)
}

// handleRESTGetNote retourne une note spécifique
// GET /api/v1/notes/{path}
// Supporte Accept: application/json ou text/markdown
func (h *Handler) handleRESTGetNote(w http.ResponseWriter, r *http.Request, notePath string) {
	fullPath := filepath.Join(h.notesDir, notePath)

	// Vérifier que le fichier existe
	info, err := os.Stat(fullPath)
	if err != nil {
		if os.IsNotExist(err) {
			h.sendJSONError(w, "Note not found", http.StatusNotFound)
		} else {
			h.logger.Printf("Erreur stat fichier %s: %v", notePath, err)
			h.sendJSONError(w, "Failed to access note", http.StatusInternalServerError)
		}
		return
	}

	// Lire le contenu
	content, err := os.ReadFile(fullPath)
	if err != nil {
		h.logger.Printf("Erreur lecture fichier %s: %v", notePath, err)
		h.sendJSONError(w, "Failed to read note", http.StatusInternalServerError)
		return
	}

	// Extraire front matter et body
	fm, body, err := indexer.ExtractFrontMatterAndBody(fullPath)
	if err != nil {
		h.logger.Printf("Erreur extraction front matter %s: %v", notePath, err)
		// Continuer sans front matter
		fm = indexer.FullFrontMatter{}
		body = string(content)
	}

	// Content negotiation
	accept := r.Header.Get("Accept")

	// Si client veut du Markdown brut
	if strings.Contains(accept, "text/markdown") || strings.Contains(accept, "text/plain") {
		w.Header().Set("Content-Type", "text/markdown; charset=utf-8")
		w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filepath.Base(notePath)))
		w.WriteHeader(http.StatusOK)
		w.Write(content)
		return
	}

	// Par défaut, retourner du JSON
	response := NoteResponse{
		Path:         filepath.ToSlash(notePath),
		Title:        fm.Title,
		Content:      string(content),
		Body:         body,
		FrontMatter:  &fm,
		LastModified: fm.LastModified,
		Size:         info.Size(),
	}

	h.sendJSON(w, response, http.StatusOK)
}

// handleRESTPutNote crée ou met à jour une note
// PUT /api/v1/notes/{path}
// Accepte: application/json, text/markdown, ou multipart/form-data
func (h *Handler) handleRESTPutNote(w http.ResponseWriter, r *http.Request, notePath string) {
	fullPath := filepath.Join(h.notesDir, notePath)
	isNewFile := false
	if _, err := os.Stat(fullPath); os.IsNotExist(err) {
		isNewFile = true
	}

	var finalContent string
	contentType := r.Header.Get("Content-Type")

	// Parser selon le Content-Type
	if strings.Contains(contentType, "application/json") {
		// JSON Request
		var req NoteRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			h.sendJSONError(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest)
			return
		}

		// Si content fourni directement, l'utiliser
		if req.Content != "" {
			finalContent = req.Content
		} else {
			// Sinon, construire depuis body + frontMatter
			var fm indexer.FullFrontMatter
			if req.FrontMatter != nil {
				fm = *req.FrontMatter
			} else {
				fm = indexer.FullFrontMatter{}
			}

			// Remplir les champs manquants
			now := time.Now()
			if fm.Title == "" {
				fm.Title = strings.TrimSuffix(filepath.Base(notePath), ".md")
				fm.Title = strings.ReplaceAll(fm.Title, "-", " ")
				fm.Title = strings.Title(fm.Title)
			}
			if isNewFile || fm.Date == "" {
				fm.Date = now.Format("02-01-2006")
			}
			fm.LastModified = now.Format("02-01-2006:15:04")
			if fm.Tags == nil {
				fm.Tags = []string{}
			}

			// Marshal front matter
			fmBytes, err := yaml.Marshal(fm)
			if err != nil {
				h.logger.Printf("Erreur marshalling front matter: %v", err)
				h.sendJSONError(w, "Failed to generate front matter", http.StatusInternalServerError)
				return
			}

			body := req.Body
			if body == "" {
				body = "\n# " + fm.Title + "\n\nVotre contenu ici..."
			}

			finalContent = "---\n" + string(fmBytes) + "---\n" + body
		}
	} else if strings.Contains(contentType, "text/markdown") || strings.Contains(contentType, "text/plain") {
		// Raw Markdown
		bodyBytes, err := io.ReadAll(r.Body)
		if err != nil {
			h.sendJSONError(w, "Failed to read body", http.StatusBadRequest)
			return
		}
		finalContent = string(bodyBytes)

		// Si pas de front matter, en générer un
		if !strings.HasPrefix(finalContent, "---") {
			now := time.Now()
			fm := indexer.FullFrontMatter{
				Title:        strings.TrimSuffix(filepath.Base(notePath), ".md"),
				Date:         now.Format("02-01-2006"),
				LastModified: now.Format("02-01-2006:15:04"),
				Tags:         []string{},
			}
			fmBytes, _ := yaml.Marshal(fm)
			finalContent = "---\n" + string(fmBytes) + "---\n" + finalContent
		} else {
			// Mettre à jour le LastModified du front matter existant
			fm, body, err := indexer.ExtractFrontMatterAndBodyFromReader(strings.NewReader(finalContent))
			if err == nil {
				fm.LastModified = time.Now().Format("02-01-2006:15:04")
				fmBytes, _ := yaml.Marshal(fm)
				finalContent = "---\n" + string(fmBytes) + "---\n" + body
			}
		}
	} else {
		h.sendJSONError(w, "Unsupported Content-Type. Use application/json or text/markdown", http.StatusUnsupportedMediaType)
		return
	}

	// Créer les dossiers parents si nécessaire
	if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil {
		h.logger.Printf("Erreur création dossier pour %s: %v", notePath, err)
		h.sendJSONError(w, "Failed to create parent directory", http.StatusInternalServerError)
		return
	}

	// Écrire le fichier
	if err := os.WriteFile(fullPath, []byte(finalContent), 0o644); err != nil {
		h.logger.Printf("Erreur écriture fichier %s: %v", notePath, err)
		h.sendJSONError(w, "Failed to write note", http.StatusInternalServerError)
		return
	}

	// Ré-indexer en arrière-plan
	go func() {
		if err := h.idx.Load(h.notesDir); err != nil {
			h.logger.Printf("Échec ré-indexation après PUT: %v", err)
		}
	}()

	// Lire les métadonnées pour la réponse
	info, _ := os.Stat(fullPath)
	fm, body, _ := indexer.ExtractFrontMatterAndBody(fullPath)

	response := NoteResponse{
		Path:         filepath.ToSlash(notePath),
		Title:        fm.Title,
		Content:      finalContent,
		Body:         body,
		FrontMatter:  &fm,
		LastModified: fm.LastModified,
		Size:         info.Size(),
	}

	statusCode := http.StatusOK
	if isNewFile {
		statusCode = http.StatusCreated
	}

	h.sendJSON(w, response, statusCode)
}

// handleRESTDeleteNote supprime une note
// DELETE /api/v1/notes/{path}
func (h *Handler) handleRESTDeleteNote(w http.ResponseWriter, r *http.Request, notePath string) {
	fullPath := filepath.Join(h.notesDir, notePath)

	// Vérifier que le fichier existe
	if _, err := os.Stat(fullPath); os.IsNotExist(err) {
		h.sendJSONError(w, "Note not found", http.StatusNotFound)
		return
	}

	// Supprimer le fichier
	if err := os.Remove(fullPath); err != nil {
		h.logger.Printf("Erreur suppression fichier %s: %v", notePath, err)
		h.sendJSONError(w, "Failed to delete note", http.StatusInternalServerError)
		return
	}

	// Ré-indexer en arrière-plan
	go func() {
		if err := h.idx.Load(h.notesDir); err != nil {
			h.logger.Printf("Échec ré-indexation après DELETE: %v", err)
		}
	}()

	// Réponse de succès
	response := map[string]interface{}{
		"message": "Note deleted successfully",
		"path":    filepath.ToSlash(notePath),
	}

	h.sendJSON(w, response, http.StatusOK)
}

// Utility functions
// =================

// sendJSON envoie une réponse JSON
func (h *Handler) sendJSON(w http.ResponseWriter, data interface{}, statusCode int) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(statusCode)

	encoder := json.NewEncoder(w)
	encoder.SetIndent("", "  ")
	if err := encoder.Encode(data); err != nil {
		h.logger.Printf("Erreur encodage JSON: %v", err)
	}
}

// sendJSONError envoie une erreur JSON
func (h *Handler) sendJSONError(w http.ResponseWriter, message string, statusCode int) {
	response := ErrorResponse{
		Error:   http.StatusText(statusCode),
		Message: message,
		Code:    statusCode,
	}
	h.sendJSON(w, response, statusCode)
}
