package main

import (
	"database/sql"
	"embed"
	"encoding/json"
	"log"
	"mime"
	"net/http"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/google/uuid"
	_ "github.com/mattn/go-sqlite3"
)

func serveStaticFile(w http.ResponseWriter, r *http.Request, prefix string) error {
	cleanPath := path.Clean(r.URL.Path)
	if cleanPath == "/" {
		cleanPath = "/index.html"
	}

	// Try to read the exact file first
	content, err := frontend.ReadFile(prefix + cleanPath)
	ext := strings.ToLower(filepath.Ext(cleanPath))

	// If file not found OR the path has no extension (likely a route path), serve index.html
	if err != nil || ext == "" {
		content, err = frontend.ReadFile(prefix + "/index.html")
		if err != nil {
			return err
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		// Add security headers for HTML content
		w.Header().Set("X-Content-Type-Options", "nosniff")
		w.Write(content)
		return nil
	}

	// For actual files, set the correct MIME type
	var mimeType string
	switch ext {
	case ".js":
		mimeType = "application/javascript; charset=utf-8"
	case ".css":
		mimeType = "text/css; charset=utf-8"
	case ".html":
		mimeType = "text/html; charset=utf-8"
	case ".json":
		mimeType = "application/json; charset=utf-8"
	case ".png":
		mimeType = "image/png"
	case ".svg":
		mimeType = "image/svg+xml"
	default:
		mimeType = mime.TypeByExtension(ext)
		if mimeType == "" {
			// Try to detect content type from the content itself
			mimeType = http.DetectContentType(content)
		}
	}

	// Set security headers for all responses
	w.Header().Set("X-Content-Type-Options", "nosniff")
	w.Header().Set("Content-Type", mimeType)

	w.Write(content)
	return nil
}

//go:embed frontend/build/* frontend/static/*
var frontend embed.FS

type Note struct {
	ID        string    `json:"id"`
	Title     string    `json:"title"`
	Content   string    `json:"content"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
}

var db *sql.DB

func main() {
	var err error
	db, err = sql.Open("sqlite3", "notes.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Create notes table
	_, err = db.Exec(`
    CREATE TABLE IF NOT EXISTS notes (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL
    )
    `)
	if err != nil {
		log.Fatal(err)
	}

	// API routes
	http.HandleFunc("/api/notes", handleNotes)
	http.HandleFunc("/api/notes/", handleNote)
	http.HandleFunc("/api/test/reset", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}
		
		_, err := db.Exec("DELETE FROM notes")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.WriteHeader(http.StatusOK)
	})

	// Serve frontend
	http.HandleFunc("/", handleFrontend)

	log.Printf("INFO: Server starting on http://localhost:3000")
	log.Fatal(http.ListenAndServe(":3000", nil))
}

func handleNotes(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		rows, err := db.Query(`
      SELECT id, title, content, created_at, updated_at 
      FROM notes 
      ORDER BY updated_at DESC
      `)
		if err != nil {
			log.Printf("ERROR: Failed to query notes: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		var notes []Note
		for rows.Next() {
			var n Note
			err := rows.Scan(&n.ID, &n.Title, &n.Content, &n.CreatedAt, &n.UpdatedAt)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			notes = append(notes, n)
		}

		json.NewEncoder(w).Encode(notes)

	case "POST":
		var note Note
		if err := json.NewDecoder(r.Body).Decode(&note); err != nil {
			log.Printf("ERROR: Failed to decode note: %v", err)
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		// Generate a new UUID for the note
		note.ID = uuid.New().String()

		_, err := db.Exec(`
			INSERT INTO notes (id, title, content, created_at, updated_at)
			VALUES (?, ?, ?, ?, ?)
			`, note.ID, note.Title, note.Content, note.CreatedAt, note.UpdatedAt)
		if err != nil {
			log.Printf("ERROR: Failed to create note: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		// Return the created note with the generated ID
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusCreated)
		json.NewEncoder(w).Encode(note)
	}
}

func handleNote(w http.ResponseWriter, r *http.Request) {
	id := path.Base(r.URL.Path)

	switch r.Method {
	case "GET":
		var note Note
		err := db.QueryRow(`
      SELECT id, title, content, created_at, updated_at 
      FROM notes 
      WHERE id = ?
      `, id).Scan(&note.ID, &note.Title, &note.Content, &note.CreatedAt, &note.UpdatedAt)
		if err == sql.ErrNoRows {
			http.NotFound(w, r)
			return
		}
		if err != nil {
			log.Printf("ERROR: Failed to get note %s: %v", id, err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		json.NewEncoder(w).Encode(note)

	case "PUT":
		var note Note
		if err := json.NewDecoder(r.Body).Decode(&note); err != nil {
			log.Printf("ERROR: Failed to decode note update: %v", err)
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		_, err := db.Exec(`
      UPDATE notes 
      SET title = ?, content = ?, updated_at = ?
      WHERE id = ?
      `, note.Title, note.Content, note.UpdatedAt, id)
		if err != nil {
			log.Printf("ERROR: Failed to update note %s: %v", id, err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

	case "DELETE":
		_, err := db.Exec("DELETE FROM notes WHERE id = ?", id)
		if err != nil {
			log.Printf("ERROR: Failed to delete note %s: %v", id, err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

func handleFrontend(w http.ResponseWriter, r *http.Request) {
	// Don't serve API routes
	if path.Dir(r.URL.Path) == "/api" {
		http.NotFound(w, r)
		return
	}

	err := serveStaticFile(w, r, "frontend/build")
	if err != nil { // if serveStaticFile returns an error, it has already tried to serve index.html as fallback
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}