package main

import (
	"embed"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"io/fs"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

//go:embed index.html
//go:embed dashboard.html
//go:embed static/*
var content embed.FS

const (
	uploadDir = "./public"
	logFile   = "debug.log"
)

type FileInfo struct {
	Name string
	Size int64
}

type AccessEvent struct {
	IP        string
	File      string
	Timestamp time.Time
	Action    string // "upload" or "download"
}

type StatEntry struct {
	IP    string `json:"ip"`
	File  string `json:"file"`
	Count int    `json:"count"`
}

var logger *log.Logger
var accessLog []AccessEvent

func main() {
	// Logger setup
	logWriter, err := os.OpenFile(logFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
	if err != nil {
		log.Fatalf("Log file error: %v", err)
	}
	defer logWriter.Close()
	logger = log.New(logWriter, "", log.LstdFlags)

	if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
		_ = os.Mkdir(uploadDir, 0755)
	}

	// Embed FS for static/ directory
	fsStatic, err := fs.Sub(content, "static")
	if err != nil {
		logger.Fatalf("Failed to load static: %v", err)
	}

	// Templates
	tmplIndex := template.Must(template.New("index.html").Funcs(template.FuncMap{
		"formatBytes": formatBytes,
	}).ParseFS(content, "index.html"))

	tmplDashboard := template.Must(template.ParseFS(content, "dashboard.html"))

	// Routes
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		files, _ := listFiles(uploadDir)
		data := struct{ Files []FileInfo }{Files: files}
		w.Header().Set("Content-Type", "text/html")
		_ = tmplIndex.Execute(w, data)
	})

	http.HandleFunc("/upload", uploadHandler)
	http.HandleFunc("/dashboard", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/html")
		_ = tmplDashboard.Execute(w, struct{ Events []AccessEvent }{accessLog})
	})

	http.HandleFunc("/dashboard-data", statsHandler)

	http.HandleFunc("/files/", func(w http.ResponseWriter, r *http.Request) {
		file := strings.TrimPrefix(r.URL.Path, "/files/")
		ip := getIP(r)
		accessLog = append(accessLog, AccessEvent{
			IP: ip, File: file, Action: "download", Timestamp: time.Now(),
		})
		http.ServeFile(w, r, filepath.Join(uploadDir, file))
	})

	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(fsStatic))))

	log.Println("Server started on http://localhost:80/")
	_ = http.ListenAndServe(":80", nil)
}

// ===== Helpers =====

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Only POST supported", http.StatusMethodNotAllowed)
		return
	}

	err := r.ParseMultipartForm(10 << 20)
	if err != nil {
		http.Error(w, "Failed to parse form", 400)
		return
	}

	file, handler, err := r.FormFile("file")
	if err != nil {
		http.Error(w, "No file found", 400)
		return
	}
	defer file.Close()

	dst, err := os.Create(filepath.Join(uploadDir, handler.Filename))
	if err != nil {
		http.Error(w, "Failed to save file", 500)
		return
	}
	defer dst.Close()
	_, _ = io.Copy(dst, file)

	ip := getIP(r)
	accessLog = append(accessLog, AccessEvent{
		IP: ip, File: handler.Filename, Action: "upload", Timestamp: time.Now(),
	})

	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func listFiles(dir string) ([]FileInfo, error) {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil, err
	}
	var files []FileInfo
	for _, e := range entries {
		info, err := e.Info()
		if err == nil && !info.IsDir() {
			files = append(files, FileInfo{Name: info.Name(), Size: info.Size()})
		}
	}
	sort.Slice(files, func(i, j int) bool {
		return files[i].Name < files[j].Name
	})
	return files, nil
}

func formatBytes(b int64) string {
	const unit = 1024
	if b < unit {
		return fmt.Sprintf("%d B", b)
	}
	div, exp := int64(unit), 0
	for n := b / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp])
}

func getIP(r *http.Request) string {
	ip := r.RemoteAddr
	if i := strings.LastIndex(ip, ":"); i != -1 {
		ip = ip[:i]
	}
	return ip
}

func statsHandler(w http.ResponseWriter, r *http.Request) {
	counter := map[string]map[string]int{}
	for _, e := range accessLog {
		if e.Action != "download" {
			continue
		}
		if counter[e.IP] == nil {
			counter[e.IP] = map[string]int{}
		}
		counter[e.IP][e.File]++
	}

	var result []StatEntry
	for ip, files := range counter {
		for file, count := range files {
			result = append(result, StatEntry{IP: ip, File: file, Count: count})
		}
	}

	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(result)
}
