package services

import (
	"encoding/json"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"

	"nadir/internal/oscmd"

	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humago"
	"github.com/danielgtaylor/huma/v2/humatest"
)

func TestServicesHandlers(t *testing.T) {
	mux := http.NewServeMux()
	api := humatest.Wrap(t, humago.New(mux, huma.DefaultConfig("Test", "1.0.0")))

	// Set up allowlisted log files for testing the file source
	logFiles := map[string][]string{
		"nginx.service": {filepath.Join(t.TempDir(), "nginx-error.log")},
	}
	// Create the dummy log file
	errLogPath := logFiles["nginx.service"][0]
	if err := os.WriteFile(errLogPath, []byte("file log line 1\nfile log line 2\n"), 0644); err != nil {
		t.Fatal(err)
	}

	m := New(logFiles)
	m.Register(api)

	// 1. Test GET /api/services (list services)
	oscmd.SetMock("systemctl", func(args []string) oscmd.MockCommand {
		if reflect.DeepEqual(args, []string{"list-units", "--type=service", "--all", "-o", "json", "--no-pager"}) {
			units := []ServiceUnit{
				{Unit: "sshd.service", Load: "loaded", Active: "active", Sub: "running", Description: "OpenSSH"},
			}
			data, _ := json.Marshal(units)
			return oscmd.MockCommand{Stdout: string(data) + "\n", ExitCode: 0}
		}
		if reflect.DeepEqual(args, []string{"show", "-p", "Id", "-p", "Description", "-p", "LoadState", "-p", "ActiveState", "-p", "SubState", "-p", "UnitFileState", "--", "sshd.service"}) {
			showOut := "Id=sshd.service\nDescription=OpenSSH\nLoadState=loaded\nActiveState=active\nSubState=running\nUnitFileState=enabled\n"
			return oscmd.MockCommand{Stdout: showOut, ExitCode: 0}
		}
		if reflect.DeepEqual(args, []string{"start", "--", "sshd.service"}) {
			return oscmd.MockCommand{ExitCode: 0}
		}
		return oscmd.MockCommand{ExitCode: 1}
	})
	defer oscmd.ClearMocks()

	resp := api.Get("/api/services")
	if resp.Code != http.StatusOK {
		t.Errorf("list services: got %d, want %d", resp.Code, http.StatusOK)
	}
	var listRes ListServicesOutput
	if err := json.Unmarshal(resp.Body.Bytes(), &listRes.Body); err != nil {
		t.Fatal(err)
	}
	if len(listRes.Body.Services) != 1 || listRes.Body.Services[0].Unit != "sshd.service" {
		t.Errorf("list services output: %+v", listRes.Body)
	}

	// 2. Test GET /api/services/{unit} (get service status)
	resp = api.Get("/api/services/sshd.service")
	if resp.Code != http.StatusOK {
		t.Errorf("get service status: got %d, want %d", resp.Code, http.StatusOK)
	}
	var getRes GetServiceOutput
	if err := json.Unmarshal(resp.Body.Bytes(), &getRes.Body); err != nil {
		t.Fatal(err)
	}
	if getRes.Body.Unit != "sshd.service" || getRes.Body.ActiveState != "active" {
		t.Errorf("get service output: %+v", getRes.Body)
	}

	// 3. Test POST /api/services/{unit}/start
	resp = api.Post("/api/services/sshd.service/start", struct{}{})
	if resp.Code != http.StatusOK {
		t.Errorf("start service: got %d, want %d", resp.Code, http.StatusOK)
	}

	// 4. Test GET /api/services/{unit}/logs (journal source)
	oscmd.SetMock("journalctl", func(args []string) oscmd.MockCommand {
		if strings.Contains(strings.Join(args, " "), "-f") {
			// Streaming mock
			lines := []string{
				`{"MESSAGE":"streaming line 1","PRIORITY":"6","__REALTIME_TIMESTAMP":"1718873704000000"}`,
			}
			return oscmd.MockCommand{Lines: lines, DelayMs: 1, ExitCode: 0}
		}
		// Regular snapshot mock
		lines := []string{
			`{"MESSAGE":"journal line 1","PRIORITY":"6","__REALTIME_TIMESTAMP":"1718873704000000"}`,
		}
		return oscmd.MockCommand{Stdout: strings.Join(lines, "\n") + "\n", ExitCode: 0}
	})

	resp = api.Get("/api/services/sshd.service/logs")
	if resp.Code != http.StatusOK {
		t.Errorf("get journal logs: got %d, want %d", resp.Code, http.StatusOK)
	}
	var logsRes LogsOutput
	if err := json.Unmarshal(resp.Body.Bytes(), &logsRes.Body); err != nil {
		t.Fatal(err)
	}
	if len(logsRes.Body.Entries) != 1 || logsRes.Body.Entries[0].Message != "journal line 1" {
		t.Errorf("journal logs output: %+v", logsRes.Body)
	}

	// 5. Test GET /api/services/{unit}/logs (file source)
	oscmd.SetMock("tail", func(args []string) oscmd.MockCommand {
		if strings.Contains(strings.Join(args, " "), "-F") {
			// Streaming mock
			return oscmd.MockCommand{Lines: []string{"stream file line 1"}, DelayMs: 1, ExitCode: 0}
		}
		return oscmd.MockCommand{Stdout: "file log line 1\nfile log line 2\n", ExitCode: 0}
	})

	resp = api.Get("/api/services/nginx.service/logs?source=file&path=" + errLogPath)
	if resp.Code != http.StatusOK {
		t.Errorf("get file logs: got %d, want %d", resp.Code, http.StatusOK)
	}
	if err := json.Unmarshal(resp.Body.Bytes(), &logsRes.Body); err != nil {
		t.Fatal(err)
	}
	if len(logsRes.Body.Entries) != 2 || logsRes.Body.Entries[0].Message != "file log line 1" {
		t.Errorf("file logs output: %+v", logsRes.Body)
	}

	// 6. Test GET /api/services/{unit}/logs/stream (journal stream)
	resp = api.Get("/api/services/sshd.service/logs/stream")
	if resp.Code != http.StatusOK {
		t.Errorf("stream journal logs: got %d, want %d", resp.Code, http.StatusOK)
	}
	bodyStr := resp.Body.String()
	if !strings.Contains(bodyStr, "streaming line 1") {
		t.Errorf("stream journal logs missing message, got: %q", bodyStr)
	}

	// 7. Test GET /api/services/{unit}/logs/stream (file stream)
	resp = api.Get("/api/services/nginx.service/logs/stream?source=file&path=" + errLogPath)
	if resp.Code != http.StatusOK {
		t.Errorf("stream file logs: got %d, want %d", resp.Code, http.StatusOK)
	}
	bodyStr = resp.Body.String()
	if !strings.Contains(bodyStr, "stream file line 1") {
		t.Errorf("stream file logs missing message, got: %q", bodyStr)
	}
}
