package main

import (
	"flag"
	"log"
	"net/http"
	"os"
	"sync"
	"time"

	"zeitkort-web/repository"
)

// Version is set at build time via -ldflags.
var Version = "dev"

const resetInterval = 10 * time.Minute

// Server holds shared dependencies.
type Server struct {
	mu         sync.Mutex // guards Cfg/resetAt when Settings are saved/reset
	Cfg        Config
	initialCfg Config
	resetAt    time.Time
	repo       repository.Repository
	info       *log.Logger
	errl       *log.Logger
}

func main() {
	cfgPath := flag.String("config", "config.json", "path to config.json")
	flag.Parse()

	info := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
	errl := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)

	cfg, err := loadConfig(*cfgPath)
	if err != nil {
		errl.Fatalf("config: %v", err)
	}

	repo := repository.NewMemoryRepository()
	if err := repo.Migrate(); err != nil {
		errl.Fatalf("repository: %v", err)
	}

	s := &Server{
		Cfg:        cfg,
		initialCfg: cfg,
		resetAt:    time.Now().Add(resetInterval),
		repo:       repo,
		info:       info,
		errl:       errl,
	}

	if err := parseTemplates(); err != nil {
		errl.Fatalf("templates: %v", err)
	}

	go s.resetLoop(resetInterval)

	info.Printf("zeitkort-web %s listening on %s (web-only POC; in-memory state resets every %s)", Version, cfg.ListenAddr, resetInterval)
	if err := http.ListenAndServe(cfg.ListenAddr, s.routes()); err != nil {
		errl.Fatal(err)
	}
}

func (s *Server) nextResetAt() time.Time {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.resetAt
}

func (s *Server) resetLoop(interval time.Duration) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for range ticker.C {
		if resetter, ok := s.repo.(interface{ Reset() }); ok {
			resetter.Reset()
		}
		s.mu.Lock()
		s.Cfg = s.initialCfg
		s.resetAt = time.Now().Add(interval)
		s.mu.Unlock()
		s.info.Printf("in-memory proof-of-concept state reset after %s", interval)
	}
}

func (s *Server) routes() http.Handler {
	mux := http.NewServeMux()

	// Static assets (css + the one small timer script), embedded in the binary.
	mux.Handle("GET /static/", staticHandler())

	// Main working area.
	mux.HandleFunc("GET /", s.handleIndex)
	mux.HandleFunc("GET /main", s.handleMain)

	// Companies.
	mux.HandleFunc("GET /companies/new", s.handleCompanyForm)
	mux.HandleFunc("GET /companies/{id}/edit", s.handleCompanyForm)
	mux.HandleFunc("POST /companies", s.handleCompanySave)
	mux.HandleFunc("POST /companies/{id}", s.handleCompanySave)
	mux.HandleFunc("POST /companies/{id}/delete", s.handleCompanyDelete)

	// Assignments.
	mux.HandleFunc("GET /assignments/new", s.handleAssignmentForm)
	mux.HandleFunc("GET /assignments/{id}/edit", s.handleAssignmentForm)
	mux.HandleFunc("POST /assignments", s.handleAssignmentSave)
	mux.HandleFunc("POST /assignments/{id}", s.handleAssignmentSave)
	mux.HandleFunc("POST /assignments/{id}/delete", s.handleAssignmentDelete)

	// Tasks.
	mux.HandleFunc("GET /tasks/new", s.handleTaskNewForm)
	mux.HandleFunc("GET /tasks/{id}/edit", s.handleTaskEditForm)
	mux.HandleFunc("POST /tasks", s.handleTaskCreate)
	mux.HandleFunc("POST /tasks/{id}", s.handleTaskUpdate)
	mux.HandleFunc("POST /tasks/{id}/delete", s.handleTaskDelete)

	// Work sessions.
	mux.HandleFunc("POST /sessions/start", s.handleSessionStart)
	mux.HandleFunc("POST /sessions/{id}/stop", s.handleSessionStop)
	mux.HandleFunc("GET /sessions/new", s.handleSessionForm)
	mux.HandleFunc("GET /sessions/{id}/edit", s.handleSessionForm)
	mux.HandleFunc("POST /sessions/manual", s.handleSessionSave)
	mux.HandleFunc("POST /sessions/{id}", s.handleSessionSave)
	mux.HandleFunc("POST /sessions/{id}/delete", s.handleSessionDelete)

	// Invoice.
	mux.HandleFunc("GET /invoice", s.handleInvoiceForm)
	mux.HandleFunc("GET /invoice/line", s.handleInvoiceLine)
	mux.HandleFunc("POST /invoice", s.handleInvoiceGenerate)

	// Settings and POC information.
	mux.HandleFunc("GET /settings", s.handleSettings)
	mux.HandleFunc("POST /settings", s.handleSettingsSave)
	mux.HandleFunc("GET /about", s.handleAbout)

	// Used by htmx to remove a row from the page (returns empty body).
	mux.HandleFunc("GET /void", func(w http.ResponseWriter, r *http.Request) {})

	return s.logging(mux)
}

func (s *Server) logging(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		next.ServeHTTP(w, r)
		s.info.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start).Round(time.Millisecond))
	})
}
