package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"

	_ "github.com/lib/pq"
)

func WriteJson(w http.ResponseWriter, v any) {
	w.Header().Set("Content-Type", "application/json")

	jsonData, err := json.Marshal(v)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Write(jsonData)
}

func handleVersion(w http.ResponseWriter, r *http.Request) {
	version := struct {
		Version string `json:version`
	}{
		Version: "0.1.0-demo",
	}

	WriteJson(w, version)
}

func InitDatabase(db *sql.DB) {
	_, err := db.Exec(
		`CREATE TABLE IF NOT EXISTS ingredient (
	id SERIAL,
	name VARCHAR(64) UNIQUE NOT NULL,
	icon VARCHAR(64),
	price INT,
	PRIMARY KEY(id)
)`)

	if err != nil {
		log.Fatal(err)
	}
	log.Print("created table for ingredients")

	_, err = db.Exec(
		`CREATE TABLE IF NOT EXISTS recipe (
	id SERIAL,
	name VARCHAR(64) UNIQUE NOT NULL,
	icon VARCHAR(64),
	PRIMARY KEY(id)
)`)

	if err != nil {
		log.Fatal(err)
	}
	log.Print("created table for receipts")

	_, err = db.Exec(
		`CREATE TABLE IF NOT EXISTS recipe_ingredient (
	recipe_id INT NOT NULL,
	ingredient_d INT NOT NULL,
	amount INT,
	FOREIGN KEY (recipe_id) REFERENCES recipe(id),
	FOREIGN KEY (ingredient_d) REFERENCES ingredient(id)
)`)

	if err != nil {
		log.Fatal(err)
	}
	log.Print("created table for recipe ingredients")
}

type Ingredient struct {
	Name  string `json:name`
	Icon  string `json:icon`
	Price int    `json:price`
}

type Recipe struct {
	Name string `json:name`
	Icon string `json:icon`
}

type RecipeIngredient struct {
	Recipe     int `json:recipe`
	Ingredient int `json:ingredient`
	Amount     int `json:amount`
}

func GetRecipes(db *sql.DB) ([]Recipe, error) {
	rows, err := db.Query("SELECT name, icon FROM recipe")
	if err != nil {
		return nil, err
	}

	recipes := []Recipe{}

	for rows.Next() {
		recipe := Recipe{}
		rows.Scan(&recipe.Name, &recipe.Icon)
		recipes = append(recipes, recipe)
	}

	return recipes, nil
}

func GetIngredients(db *sql.DB) ([]Ingredient, error) {
	rows, err := db.Query("SELECT name, icon, price FROM ingredient")
	if err != nil {
		return nil, err
	}

	ingredients := []Ingredient{}

	for rows.Next() {
		ingredient := Ingredient{}
		rows.Scan(&ingredient.Name, &ingredient.Icon, &ingredient.Price)
		ingredients = append(ingredients, ingredient)
	}

	return ingredients, nil
}

func GetRecipeIngredients(recipe_name string, db *sql.DB) ([]Ingredient, error) {
	rows, err := db.Query(fmt.Sprintf("SELECT id FROM recipe WHERE name='%s'", recipe_name))
	if err != nil {
		return nil, err
	}

	id := 0
	if rows.Next() {
		rows.Scan(&id)
	} else {
		return nil, nil
	}

	rows, err = db.Query(fmt.Sprintf("SELECT ingredient.name, ingredient.icon, ingredient.price FROM ingredient INNER JOIN recipe_ingredient ON recipe_ingredient.ingredient_d=ingredient.id WHERE recipe_ingredient.recipe_id=%d", id))
	if err != nil {
		return nil, err
	}

	ingredients := []Ingredient{}

	for rows.Next() {
		ingredient := Ingredient{}
		rows.Scan(&ingredient.Name, &ingredient.Icon, &ingredient.Price)
		ingredients = append(ingredients, ingredient)
	}

	return ingredients, nil
}

func HandleApiRecipes(w http.ResponseWriter, r *http.Request, db *sql.DB) {
	if r.Method != http.MethodGet && r.Method != "" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		log.Print("Mismatched method: ", r.Method)
		return
	}

	recipes, err := GetRecipes(db)
	if err != nil  {
		http.Error(w, "Internal server error", http.StatusInternalServerError)
		log.Print("Error fetching recipes: ", err)
		return
	}

	WriteJson(w, recipes)

	log.Print("Fetched ", len(recipes), " recipes from database")
}

func HandleApiIngredients(w http.ResponseWriter, r *http.Request, db *sql.DB) {
	if r.Method != http.MethodGet && r.Method != "" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		log.Print("Mismatched method: ", r.Method)
		return
	}

	ingredients, err := GetIngredients(db)
	if err != nil {
		http.Error(w, "Internal server error", http.StatusInternalServerError)
		log.Print("Error fetching ingredients: ", err)
		return
	}

	log.Print("Fetched ", len(ingredients), " ingredients from database")

	WriteJson(w, ingredients)
}

func HandleApiRecipeIngredients(w http.ResponseWriter, r *http.Request, db *sql.DB) {
	if r.Method != http.MethodGet && r.Method != "" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		log.Print("Mismatched method: ", r.Method)
		return
	}

	recipe_name := r.PathValue("name")
	log.Print("Fetching ingredients for recipe ", recipe_name)

	ingredients, err := GetRecipeIngredients(recipe_name, db)
	if err != nil {
		http.Error(w, "Internal server error", http.StatusInternalServerError)
		log.Print("Error fetching ingredients: ", err)
		return
	}

	if ingredients == nil {
		http.NotFound(w, r)
		return
	}

	WriteJson(w, ingredients)
}

func HandleApiIngedientAdd(w http.ResponseWriter, r *http.Request, db *sql.DB) {
	if r.Method != http.MethodGet && r.Method != "" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		log.Print("Mismatched method: ", r.Method)
		return
	}


}

func HandleGetImage(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet && r.Method != "" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		log.Print("Mismatched method: ", r.Method)
		return
	}

	var imageSearchPath = os.Getenv("GREPFOOD_IMAGE_LOCAL_STORAGE_PATH");

	bytes, err := os.ReadFile(filepath.Join(imageSearchPath, r.PathValue("name")))
 	if err != nil {
        log.Print(err)
        http.Error(w, "File not found", http.StatusNotFound)
    }

    w.Header().Set("Content-Type", "image/png")
    w.Write(bytes)
}

func main() {
	dbconn := os.Getenv("GREPFOOD_DATABASE_CONNECTION")

	db, err := sql.Open("postgres", dbconn)
	if err != nil {
		log.Fatal(err)
	}
	log.Print("connected to database: ", dbconn)

	InitDatabase(db)

	http.HandleFunc("/version", handleVersion)
	http.Handle("/", http.FileServer(http.Dir("static")))
	http.HandleFunc("/api/recipes", func(w http.ResponseWriter, r *http.Request) {
		HandleApiRecipes(w, r, db)
	})
	http.HandleFunc("/api/ingredients", func(w http.ResponseWriter, r *http.Request) {
		HandleApiIngredients(w, r, db)
	})
	http.HandleFunc("/api/recipe/{name}/ingredients", func(w http.ResponseWriter, r *http.Request) {
		HandleApiRecipeIngredients(w, r, db)
	})
	http.HandleFunc("/api/image/{name}", func(w http.ResponseWriter, r *http.Request) {
		HandleGetImage(w, r)
	})

	log.Fatal(http.ListenAndServe(os.Getenv("GREPFOOD_SYNCSERVER_PORT"), nil))
}
