package main

import (
	"bytes"
	"log"
	"main/cache"
	"main/config"
	"main/tools"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"
)

/*
sudo systemctl stop server.service
go build main.go
sudo systemctl start server.service
*/

// App представляет основное состояние приложения
type app struct {
	templates *template.Template // Шаблоны страниц
	config    *config.Config     // Конфиг
	startTime int64              // Время запуска
	cache     *cache.Cache       // Кэш (отрендеренные странички)
}

func main() {
	var app *app
	var err error

	// Инициализация приложения
	if app, err = appInit("config.json"); err != nil {
		log.Fatal(err)
	}

	// Настройка маршрутов и запуск
	if app.setupRouterAndRun() != nil {
		log.Fatal(err)
	}
}

// Запускает сервер на указанном IP и порту
func runServer(ip, port string, router http.Handler) {
	addr := ip + port
	log.Println("Run on", addr)
	log.Fatal(http.ListenAndServe(addr, router))
}

// Инициализирует приложение
func appInit(configPath string) (*app, error) {

	a := &app{
		startTime: time.Now().Unix(),
		config:    config.Init(),
		cache:     cache.Init(),
	}

	// Загрузка конфига
	if err := a.config.Load(configPath); err != nil {
		return nil, err
	}

	// Загрузка шаблонов
	if err := a.loadTemplates(a.config.TemplatesPath, a.config.TemplatesExt); err != nil {
		log.Fatal(err)
	}

	return a, nil
}

func (a *app) setupRouterAndRun() error {
	// Настройка маршрутов
	router := a.setupRouter()

	// Запуск сервера
	if ok, err := tools.IsIPInUse(a.config.ServerIP); err != nil {
		return err
	} else if ok {
		runServer(a.config.ServerIP, a.config.Port, router)
	} else {
		runServer(a.config.LocalIP, a.config.Port, router)
	}

	return nil
}

// Настраивает маршруты
func (a *app) setupRouter() *http.ServeMux {
	router := http.NewServeMux()

	// Обработка статических файлов с кэшированием
	router.Handle(a.config.AssetsPath, a.staticHandler())

	// Обработка главной страницы
	router.Handle("/", a.mainPageHandler())

	return router
}

// Обработчик статических файлов с кэшированием
func (a *app) staticHandler() http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Cache-Control", "public, max-age=31536000, immutable")
		// Здесь используется встроенный файловый сервер Go (http.FileServer), который:
		// Реализует интерфейс http.Handler (и поэтому имеет метод ServeHTTP)
		// Автоматически обслуживает статические файлы из файловой системы
		// Сам обрабатывает HTTP-запросы, определяет MIME-типы, отправляет правильные заголовки и т.д.
		http.FileServer(http.Dir(".")).ServeHTTP(w, r)
	})
}

// Обработчик главной страницы
func (a *app) mainPageHandler() http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var err error
		tmplName := "main" + a.config.TemplatesExt

		// Страничка рендерится только если ее нет в кэше
		pageData, ok := a.cache.Get(tmplName)
		if !ok {
			context := map[string]any{
				"version":            a.startTime,
				"renderingTimestamp": time.Now().Unix(),
			}
			pageData, err = a.renderPage(tmplName, context)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			a.cache.Set(tmplName, pageData)
		}

		a.sendPage(w, pageData.([]byte))
	})
}

// Загрузка шаблонов
func (a *app) loadTemplates(templatesPath string, ext string) error {
	tmpls := template.New("")

	err := filepath.Walk(templatesPath, func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !f.IsDir() && strings.HasSuffix(f.Name(), ext) {
			_, err = tmpls.ParseFiles(path)
			if err != nil {
				return err
			}
		}
		return nil
	})

	if err != nil {
		return err
	}

	a.templates = tmpls
	return nil
}

// Рендерит шаблон в срез байт
func (a *app) renderPage(tmplName string, context any) ([]byte, error) {
	var pageData bytes.Buffer

	if err := a.templates.ExecuteTemplate(&pageData, tmplName, context); err != nil {
		return nil, err
	}

	return pageData.Bytes(), nil
}

// Отправляет страницу
func (a *app) sendPage(w http.ResponseWriter, data []byte) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	w.Write(data)
}