package api

import (
	"bufio"
	"math"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"time"

	"archivmail/internal/audit"
	"archivmail/internal/storage"
	"archivmail/pkg/mailparser"
)

// ── Mail Timeseries handler ───────────────────────────────────────────────

// handleMailTimeseries returns daily mail counts for the last 30 days.
// GET /api/admin/stats/timeseries?days=30
func (s *Server) handleMailTimeseries(w http.ResponseWriter, r *http.Request) {
	days := 30
	sess := sessionFromCtx(r.Context())
	tenantID := tenantFromCtx(r.Context())

	// domain_admin sees only own tenant; superadmin sees all
	var tid *int64
	if sess.TenantID != nil {
		tid = tenantID
	}

	points, err := s.store.MailTimeseries(r.Context(), days, tid)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "timeseries query failed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{
		"days":   days,
		"points": points,
	})
}

// ── Audit Log handler ─────────────────────────────────────────────────────

func (s *Server) handleAuditLog(w http.ResponseWriter, r *http.Request) {
	pageStr := r.URL.Query().Get("page")
	pageSizeStr := r.URL.Query().Get("page_size")
	username := r.URL.Query().Get("username")
	eventType := r.URL.Query().Get("event_type")

	page, _ := strconv.Atoi(pageStr)
	pageSize, _ := strconv.Atoi(pageSizeStr)
	if pageSize <= 0 {
		pageSize = 50
	}

	// PROJ-55: tenant isolation for the audit log. Any role with a tenant_id
	// (domain_admin, domain_auditor, and a tenant-scoped auditor) sees only the
	// audit entries of its own tenant. superadmin has no tenant_id and stays
	// unfiltered (sees all tenants). A legacy global auditor (no tenant_id) is
	// likewise unfiltered — its access scope is unchanged.
	sess := sessionFromCtx(r.Context())
	filter := audit.QueryFilter{
		Username:  username,
		EventType: eventType,
		PageSize:  pageSize,
		Page:      page,
	}
	if sess.TenantID != nil {
		filter.TenantID = sess.TenantID
	}

	entries, total, err := s.audlog.Query(filter)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "audit query failed")
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"total":   total,
		"entries": entries,
	})
}

// ── System stats handler ─────────────────────────────────────────────────

type diskStat struct {
	Mount      string  `json:"mount"`
	TotalBytes uint64  `json:"total_bytes"`
	UsedBytes  uint64  `json:"used_bytes"`
	FreeBytes  uint64  `json:"free_bytes"`
	UsedPct    float64 `json:"used_pct"`
	FSType     string  `json:"fstype"`
}

type mailInfo struct {
	ID      string `json:"id"`
	Date    string `json:"date"`
	From    string `json:"from"`
	Subject string `json:"subject"`
}

var excludedFSTypes = map[string]bool{
	"tmpfs": true, "proc": true, "sysfs": true, "devtmpfs": true,
	"cgroup": true, "cgroup2": true, "overlay": true, "squashfs": true,
	"debugfs": true, "tracefs": true, "securityfs": true, "pstore": true,
	"efivarfs": true, "bpf": true, "hugetlbfs": true, "mqueue": true,
	"ramfs": true, "devpts": true, "fusectl": true, "configfs": true,
	"autofs": true, "nsfs": true, "rpc_pipefs": true,
	"fuse.lxcfs": true, "fuse": true,
}

func (s *Server) handleSystemStats(w http.ResponseWriter, r *http.Request) {
	// CPU: /proc/loadavg
	cpuResp := map[string]interface{}{"load1": 0.0, "load5": 0.0, "load15": 0.0, "num_cpu": runtime.NumCPU()}
	if data, err := os.ReadFile("/proc/loadavg"); err == nil {
		parts := strings.Fields(string(data))
		if len(parts) >= 3 {
			l1, _ := strconv.ParseFloat(parts[0], 64)
			l5, _ := strconv.ParseFloat(parts[1], 64)
			l15, _ := strconv.ParseFloat(parts[2], 64)
			cpuResp = map[string]interface{}{"load1": l1, "load5": l5, "load15": l15, "num_cpu": runtime.NumCPU()}
		}
	}

	// RAM: /proc/meminfo
	ramResp := map[string]interface{}{"total_bytes": uint64(0), "used_bytes": uint64(0), "free_bytes": uint64(0), "used_pct": 0.0}
	if data, err := os.ReadFile("/proc/meminfo"); err == nil {
		kv := parseMeminfo(string(data))
		total := kv["MemTotal"] * 1024
		available := kv["MemAvailable"] * 1024
		used := total - available
		var usedPct float64
		if total > 0 {
			usedPct = math.Round(float64(used)/float64(total)*1000) / 10
		}
		ramResp = map[string]interface{}{
			"total_bytes": total,
			"used_bytes":  used,
			"free_bytes":  available,
			"used_pct":    usedPct,
		}
	}

	// Disks: /proc/mounts + syscall.Statfs
	var disks []diskStat
	seenMounts := map[string]bool{}  // deduplicate by mountpoint
	seenDevices := map[string]bool{} // deduplicate by device (catches ZFS bind-mounts)
	if data, err := os.ReadFile("/proc/mounts"); err == nil {
		scanner := bufio.NewScanner(strings.NewReader(string(data)))
		for scanner.Scan() {
			fields := strings.Fields(scanner.Text())
			if len(fields) < 3 {
				continue
			}
			device := fields[0]
			mount := fields[1]
			fstype := fields[2]
			if excludedFSTypes[fstype] || seenMounts[mount] || seenDevices[device] {
				continue
			}
			seenMounts[mount] = true
			var stat syscall.Statfs_t
			if err := syscall.Statfs(mount, &stat); err != nil {
				continue
			}
			total := stat.Blocks * uint64(stat.Bsize)
			free := stat.Bavail * uint64(stat.Bsize)
			used := total - free
			if total == 0 {
				continue // skip pseudo-mounts with no storage (e.g. lxcfs overlays)
			}
			seenDevices[device] = true
			var usedPct float64
			if total > 0 {
				usedPct = math.Round(float64(used)/float64(total)*1000) / 10
			}
			disks = append(disks, diskStat{
				Mount:      mount,
				TotalBytes: total,
				UsedBytes:  used,
				FreeBytes:  free,
				UsedPct:    usedPct,
				FSType:     fstype,
			})
		}
	}
	if disks == nil {
		disks = []diskStat{}
	}

	// Uptime: /proc/uptime
	uptimeResp := map[string]interface{}{"seconds": 0.0}
	if data, err := os.ReadFile("/proc/uptime"); err == nil {
		parts := strings.Fields(string(data))
		if len(parts) >= 1 {
			if secs, err := strconv.ParseFloat(parts[0], 64); err == nil {
				uptimeResp["seconds"] = secs
			}
		}
	}

	// Archive: first & last mail
	archiveResp := map[string]interface{}{"first_mail": nil, "last_mail": nil}
	first, last, err := s.store.FirstAndLastMail()
	if err == nil {
		if first != nil {
			archiveResp["first_mail"] = mailRefToInfo(s.store, first)
		}
		if last != nil {
			archiveResp["last_mail"] = mailRefToInfo(s.store, last)
		}
	}

	// Mail activity (60min / 24h / 7d / 30d)
	activity, _ := s.store.MailActivityStats(r.Context())

	// Storage estimate — use free bytes of the first non-root disk (or root)
	var freeDiskBytes uint64
	for _, d := range disks {
		freeDiskBytes = d.FreeBytes
		break
	}
	estimate, _ := s.store.StorageEstimateStats(r.Context(), freeDiskBytes)

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"cpu":      cpuResp,
		"ram":      ramResp,
		"disks":    disks,
		"uptime":   uptimeResp,
		"archive":  archiveResp,
		"activity": activity,
		"estimate": estimate,
		// PROJ-49: At-Rest-Verschlüsselungsstatus (systemweit, ein Keyfile für
		// alle Tenants). enabled=false → Dashboard zeigt Warn-Badge.
		"encryption": map[string]interface{}{
			"enabled": s.store.EncryptionEnabled(),
		},
	})
}

func parseMeminfo(content string) map[string]uint64 {
	result := make(map[string]uint64)
	scanner := bufio.NewScanner(strings.NewReader(content))
	for scanner.Scan() {
		k, v, ok := strings.Cut(scanner.Text(), ":")
		if !ok {
			continue
		}
		fields := strings.Fields(strings.TrimSpace(v))
		if len(fields) == 0 {
			continue
		}
		val, err := strconv.ParseUint(fields[0], 10, 64)
		if err == nil {
			result[k] = val
		}
	}
	return result
}

func mailRefToInfo(store *storage.Store, ref *storage.MailRef) *mailInfo {
	dateStr := ref.ModTime.UTC().Format(time.RFC3339)
	raw, err := store.Load(ref.ID)
	if err != nil {
		return &mailInfo{ID: ref.ID, Date: dateStr}
	}
	pm, err := mailparser.Parse(raw)
	if err != nil {
		return &mailInfo{ID: ref.ID, Date: dateStr}
	}
	if !pm.Date.IsZero() {
		dateStr = pm.Date.UTC().Format(time.RFC3339)
	}
	return &mailInfo{
		ID:      ref.ID,
		Date:    dateStr,
		From:    pm.From,
		Subject: pm.Subject,
	}
}
