package main

import (
	"context"
	"fmt"
)

// App struct
type App struct {
	ctx     context.Context
	updater *Updater
}

// NewApp creates a new App application struct
func NewApp() *App {
	return &App{
		updater: NewUpdater(),
	}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
	a.ctx = ctx
}

// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
	return fmt.Sprintf("Hello %s, It's show time!", name)
}

// PRODUKTE

// GetProducts gibt alle Produkte zurück
func (a *App) GetProducts() ([]Product, error) {
	query := `
		SELECT p.id, p.name, p.multiline
		FROM products p
		ORDER BY p.name
	`

	var products []Product
	err := db.Select(&products, query)
	if err != nil {
		return nil, fmt.Errorf("failed to get products: %w", err)
	}

	// Allergene und Zusatzstoffe für jedes Produkt laden
	for i := range products {
		if err := loadProductRelations(&products[i]); err != nil {
			return nil, err
		}
	}

	return products, nil
}

// GetProduct gibt ein einzelnes Produkt zurück
func (a *App) GetProduct(id int) (*Product, error) {
	var product Product
	query := "SELECT id, name, multiline FROM products WHERE id = ?"
	err := db.Get(&product, query, id)
	if err != nil {
		return nil, fmt.Errorf("failed to get product: %w", err)
	}

	if err := loadProductRelations(&product); err != nil {
		return nil, err
	}

	return &product, nil
}

// CreateProduct erstellt ein neues Produkt
func (a *App) CreateProduct(name string, multiline bool, allergenIDs []string, additiveIDs []string) (*Product, error) {
	tx, err := db.Beginx()
	if err != nil {
		return nil, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback()

	// Produkt einfügen
	result, err := tx.Exec("INSERT INTO products (name, multiline) VALUES (?, ?)", name, multiline)
	if err != nil {
		return nil, fmt.Errorf("failed to insert product: %w", err)
	}

	productID, err := result.LastInsertId()
	if err != nil {
		return nil, fmt.Errorf("failed to get product ID: %w", err)
	}

	// Allergene zuordnen
	for _, allergenID := range allergenIDs {
		_, err := tx.Exec("INSERT INTO product_allergens (product_id, allergen_id) VALUES (?, ?)", productID, allergenID)
		if err != nil {
			return nil, fmt.Errorf("failed to link allergen: %w", err)
		}
	}

	// Zusatzstoffe zuordnen
	for _, additiveID := range additiveIDs {
		_, err := tx.Exec("INSERT INTO product_additives (product_id, additive_id) VALUES (?, ?)", productID, additiveID)
		if err != nil {
			return nil, fmt.Errorf("failed to link additive: %w", err)
		}
	}

	if err := tx.Commit(); err != nil {
		return nil, fmt.Errorf("failed to commit transaction: %w", err)
	}

	return a.GetProduct(int(productID))
}

// UpdateProduct aktualisiert ein Produkt
func (a *App) UpdateProduct(id int, name string, multiline bool, allergenIDs []string, additiveIDs []string) (*Product, error) {
	tx, err := db.Beginx()
	if err != nil {
		return nil, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback()

	// Produkt aktualisieren
	_, err = tx.Exec("UPDATE products SET name = ?, multiline = ? WHERE id = ?", name, multiline, id)
	if err != nil {
		return nil, fmt.Errorf("failed to update product: %w", err)
	}

	// Alte Zuordnungen löschen
	_, err = tx.Exec("DELETE FROM product_allergens WHERE product_id = ?", id)
	if err != nil {
		return nil, fmt.Errorf("failed to delete old allergen links: %w", err)
	}

	_, err = tx.Exec("DELETE FROM product_additives WHERE product_id = ?", id)
	if err != nil {
		return nil, fmt.Errorf("failed to delete old additive links: %w", err)
	}

	// Neue Allergene zuordnen
	for _, allergenID := range allergenIDs {
		_, err := tx.Exec("INSERT INTO product_allergens (product_id, allergen_id) VALUES (?, ?)", id, allergenID)
		if err != nil {
			return nil, fmt.Errorf("failed to link allergen: %w", err)
		}
	}

	// Neue Zusatzstoffe zuordnen
	for _, additiveID := range additiveIDs {
		_, err := tx.Exec("INSERT INTO product_additives (product_id, additive_id) VALUES (?, ?)", id, additiveID)
		if err != nil {
			return nil, fmt.Errorf("failed to link additive: %w", err)
		}
	}

	if err := tx.Commit(); err != nil {
		return nil, fmt.Errorf("failed to commit transaction: %w", err)
	}

	return a.GetProduct(id)
}

// DeleteProduct löscht ein Produkt
func (a *App) DeleteProduct(id int) error {
	_, err := db.Exec("DELETE FROM products WHERE id = ?", id)
	if err != nil {
		return fmt.Errorf("failed to delete product: %w", err)
	}
	return nil
}

// ALLERGENE & ZUSATZSTOFFE

// GetAllergens gibt alle Allergene zurück
func (a *App) GetAllergens() ([]Allergen, error) {
	var allergens []Allergen
	err := db.Select(&allergens, "SELECT id, name, category FROM allergens ORDER BY id")
	if err != nil {
		return nil, fmt.Errorf("failed to get allergens: %w", err)
	}
	return allergens, nil
}

// GetAdditives gibt alle Zusatzstoffe zurück
func (a *App) GetAdditives() ([]Additive, error) {
	var additives []Additive
	err := db.Select(&additives, "SELECT id, name FROM additives ORDER BY id")
	if err != nil {
		return nil, fmt.Errorf("failed to get additives: %w", err)
	}
	return additives, nil
}

// WOCHENPLÄNE

// GetWeekPlan gibt einen Wochenplan zurück
func (a *App) GetWeekPlan(year int, week int) (*WeekPlan, error) {
	var plan WeekPlan
	query := "SELECT id, year, week, created_at FROM week_plans WHERE year = ? AND week = ?"
	err := db.Get(&plan, query, year, week)
	if err != nil {
		return nil, fmt.Errorf("failed to get week plan: %w", err)
	}

	// Einträge laden
	entries, err := a.loadPlanEntries(plan.ID)
	if err != nil {
		return nil, err
	}
	plan.Entries = entries

	// Sondertage laden
	specialDays, err := a.loadSpecialDays(plan.ID)
	if err != nil {
		return nil, err
	}
	plan.SpecialDays = specialDays

	return &plan, nil
}

// CreateWeekPlan erstellt einen neuen Wochenplan
func (a *App) CreateWeekPlan(year int, week int) (*WeekPlan, error) {
	_, err := db.Exec("INSERT INTO week_plans (year, week) VALUES (?, ?)", year, week)
	if err != nil {
		return nil, fmt.Errorf("failed to create week plan: %w", err)
	}

	return a.GetWeekPlan(year, week)
}

// CopyWeekPlan kopiert einen Wochenplan
func (a *App) CopyWeekPlan(sourceYear int, sourceWeek int, targetYear int, targetWeek int) (*WeekPlan, error) {
	// Erst Zielplan erstellen
	targetPlan, err := a.CreateWeekPlan(targetYear, targetWeek)
	if err != nil {
		return nil, err
	}

	// Quellplan laden
	sourcePlan, err := a.GetWeekPlan(sourceYear, sourceWeek)
	if err != nil {
		return nil, err
	}

	tx, err := db.Beginx()
	if err != nil {
		return nil, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback()

	// Einträge kopieren
	for _, entry := range sourcePlan.Entries {
		_, err := tx.Exec(`
			INSERT INTO plan_entries (week_plan_id, day, meal, slot, product_id, custom_text, group_label)
			VALUES (?, ?, ?, ?, ?, ?, ?)
		`, targetPlan.ID, entry.Day, entry.Meal, entry.Slot, entry.ProductID, entry.CustomText, entry.GroupLabel)
		if err != nil {
			return nil, fmt.Errorf("failed to copy plan entry: %w", err)
		}
	}

	// Sondertage kopieren
	for _, special := range sourcePlan.SpecialDays {
		_, err := tx.Exec(`
			INSERT INTO special_days (week_plan_id, day, type, label)
			VALUES (?, ?, ?, ?)
		`, targetPlan.ID, special.Day, special.Type, special.Label)
		if err != nil {
			return nil, fmt.Errorf("failed to copy special day: %w", err)
		}
	}

	if err := tx.Commit(); err != nil {
		return nil, fmt.Errorf("failed to commit transaction: %w", err)
	}

	return a.GetWeekPlan(targetYear, targetWeek)
}

// PLAN-EINTRÄGE

// AddPlanEntry fügt einen Planeintrag hinzu
func (a *App) AddPlanEntry(weekPlanID int, day int, meal string, productID *int, customText *string, groupLabel *string) (*PlanEntry, error) {
	// Nächste Slot-Nummer ermitteln
	var maxSlot int
	err := db.Get(&maxSlot, "SELECT COALESCE(MAX(slot), -1) FROM plan_entries WHERE week_plan_id = ? AND day = ? AND meal = ?", weekPlanID, day, meal)
	if err != nil {
		return nil, fmt.Errorf("failed to get max slot: %w", err)
	}

	slot := maxSlot + 1

	result, err := db.Exec(`
		INSERT INTO plan_entries (week_plan_id, day, meal, slot, product_id, custom_text, group_label)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`, weekPlanID, day, meal, slot, productID, customText, groupLabel)
	if err != nil {
		return nil, fmt.Errorf("failed to add plan entry: %w", err)
	}

	entryID, err := result.LastInsertId()
	if err != nil {
		return nil, fmt.Errorf("failed to get entry ID: %w", err)
	}

	return a.getPlanEntry(int(entryID))
}

// RemovePlanEntry entfernt einen Planeintrag
func (a *App) RemovePlanEntry(id int) error {
	_, err := db.Exec("DELETE FROM plan_entries WHERE id = ?", id)
	if err != nil {
		return fmt.Errorf("failed to remove plan entry: %w", err)
	}
	return nil
}

// UpdatePlanEntry aktualisiert einen Planeintrag
func (a *App) UpdatePlanEntry(id int, productID *int, customText *string, groupLabel *string) (*PlanEntry, error) {
	_, err := db.Exec(`
		UPDATE plan_entries 
		SET product_id = ?, custom_text = ?, group_label = ?
		WHERE id = ?
	`, productID, customText, groupLabel, id)
	if err != nil {
		return nil, fmt.Errorf("failed to update plan entry: %w", err)
	}

	return a.getPlanEntry(id)
}

// SONDERTAGE

// SetSpecialDay setzt einen Sondertag
func (a *App) SetSpecialDay(weekPlanID int, day int, dtype string, label string) error {
	_, err := db.Exec(`
		INSERT OR REPLACE INTO special_days (week_plan_id, day, type, label)
		VALUES (?, ?, ?, ?)
	`, weekPlanID, day, dtype, label)
	if err != nil {
		return fmt.Errorf("failed to set special day: %w", err)
	}
	return nil
}

// RemoveSpecialDay entfernt einen Sondertag
func (a *App) RemoveSpecialDay(weekPlanID int, day int) error {
	_, err := db.Exec("DELETE FROM special_days WHERE week_plan_id = ? AND day = ?", weekPlanID, day)
	if err != nil {
		return fmt.Errorf("failed to remove special day: %w", err)
	}
	return nil
}

// OTA UPDATE

// CheckForUpdate prüft auf verfügbare Updates
func (a *App) CheckForUpdate() (*UpdateInfo, error) {
	return a.updater.CheckForUpdate()
}

// DownloadUpdate lädt ein verfügbares Update herunter
func (a *App) DownloadUpdate() (string, error) {
	info, err := a.updater.CheckForUpdate()
	if err != nil {
		return "", fmt.Errorf("Update-Check fehlgeschlagen: %w", err)
	}
	if !info.Available {
		return "Kein Update verfügbar.", nil
	}
	return a.updater.DownloadUpdate(*info)
}

// PDF EXPORT

// ExportPDF erstellt eine PDF-Datei des Wochenplans
// getWeekPlanByID lädt einen Wochenplan anhand seiner ID
func (a *App) getWeekPlanByID(id int) (*WeekPlan, error) {
	var plan WeekPlan
	query := "SELECT id, year, week, created_at FROM week_plans WHERE id = ?"
	err := db.Get(&plan, query, id)
	if err != nil {
		return nil, fmt.Errorf("failed to get week plan by ID: %w", err)
	}
	return &plan, nil
}

// HILFSFUNKTIONEN

// loadProductRelations lädt Allergene und Zusatzstoffe für ein Produkt
func loadProductRelations(product *Product) error {
	// Allergene laden
	allergenQuery := `
		SELECT a.id, a.name, a.category
		FROM allergens a
		JOIN product_allergens pa ON a.id = pa.allergen_id
		WHERE pa.product_id = ?
		ORDER BY a.id
	`
	err := db.Select(&product.Allergens, allergenQuery, product.ID)
	if err != nil {
		return fmt.Errorf("failed to load allergens for product %d: %w", product.ID, err)
	}

	// Zusatzstoffe laden
	additiveQuery := `
		SELECT a.id, a.name
		FROM additives a
		JOIN product_additives pa ON a.id = pa.additive_id
		WHERE pa.product_id = ?
		ORDER BY a.id
	`
	err = db.Select(&product.Additives, additiveQuery, product.ID)
	if err != nil {
		return fmt.Errorf("failed to load additives for product %d: %w", product.ID, err)
	}

	return nil
}

// loadPlanEntries lädt Einträge für einen Wochenplan
func (a *App) loadPlanEntries(weekPlanID int) ([]PlanEntry, error) {
	query := `
		SELECT pe.id, pe.week_plan_id, pe.day, pe.meal, pe.slot, 
			   pe.product_id, pe.custom_text, pe.group_label
		FROM plan_entries pe
		WHERE pe.week_plan_id = ?
		ORDER BY pe.day, pe.meal, pe.slot
	`

	var entries []PlanEntry
	err := db.Select(&entries, query, weekPlanID)
	if err != nil {
		return nil, fmt.Errorf("failed to load plan entries: %w", err)
	}

	// Produkte laden
	for i := range entries {
		if entries[i].ProductID != nil {
			product, err := a.GetProduct(*entries[i].ProductID)
			if err != nil {
				return nil, err
			}
			entries[i].Product = product
		}
	}

	return entries, nil
}

// loadSpecialDays lädt Sondertage für einen Wochenplan
func (a *App) loadSpecialDays(weekPlanID int) ([]SpecialDay, error) {
	var specialDays []SpecialDay
	query := `
		SELECT id, week_plan_id, day, type, label
		FROM special_days
		WHERE week_plan_id = ?
		ORDER BY day
	`
	err := db.Select(&specialDays, query, weekPlanID)
	if err != nil {
		return nil, fmt.Errorf("failed to load special days: %w", err)
	}
	return specialDays, nil
}

// getPlanEntry lädt einen einzelnen Planeintrag
func (a *App) getPlanEntry(id int) (*PlanEntry, error) {
	var entry PlanEntry
	query := `
		SELECT id, week_plan_id, day, meal, slot, product_id, custom_text, group_label
		FROM plan_entries
		WHERE id = ?
	`
	err := db.Get(&entry, query, id)
	if err != nil {
		return nil, fmt.Errorf("failed to get plan entry: %w", err)
	}

	// Produkt laden wenn vorhanden
	if entry.ProductID != nil {
		product, err := a.GetProduct(*entry.ProductID)
		if err != nil {
			return nil, err
		}
		entry.Product = product
	}

	return &entry, nil
}
