package system

import (
	"context"
	"slices"
	"strconv"
	"strings"
	"time"

	"nadir/internal/oscmd"

	"github.com/danielgtaylor/huma/v2"
)

type TimeStatusBody struct {
	Timezone        string `json:"timezone" example:"Europe/Rome" doc:"IANA timezone name"`
	LocalRTC        bool   `json:"local_rtc" doc:"Hardware clock kept in local time instead of UTC"`
	NTP             bool   `json:"ntp" doc:"Network time synchronization enabled"`
	NTPSynchronized bool   `json:"ntp_synchronized" doc:"Clock is currently synchronized"`
	CanNTP          bool   `json:"can_ntp" doc:"An NTP service is available on this host"`
	Time            string `json:"time" example:"2026-06-19T13:36:31Z" doc:"Current system time (RFC3339, UTC)"`
}

type GetTimeOutput struct{ Body TimeStatusBody }

type TimezonesOutput struct {
	Body struct {
		Timezones []string `json:"timezones" doc:"Available IANA timezone names"`
	}
}

type SetTimezoneInput struct {
	Body struct {
		Timezone string `json:"timezone" example:"Europe/Rome" doc:"IANA timezone name"`
	}
}

type SetNTPInput struct {
	Body struct {
		Enabled bool `json:"enabled" doc:"Enable network time synchronization"`
	}
}

type SetTimeInput struct {
	Body struct {
		Time string `json:"time" example:"2026-06-19T13:36:31Z" doc:"New time (RFC3339). Requires NTP disabled."`
	}
}

func timedatectlShow() (map[string]string, error) {
	lines, err := oscmd.RunLines("timedatectl", "show")
	if err != nil {
		return nil, err
	}
	return oscmd.ParseKV(lines), nil
}

// readTimeStatus builds the current time/timezone/sync status. Shared by the
// GET endpoint and the write endpoints, which return the resulting state so
// clients render ground truth instead of guessing after an opaque "ok". Note
// NTPSynchronized lags NTP by seconds-to-minutes while the NTP daemon converges.
func readTimeStatus() (TimeStatusBody, error) {
	m, err := timedatectlShow()
	if err != nil {
		return TimeStatusBody{}, err
	}
	return TimeStatusBody{
		Timezone:        m["Timezone"],
		LocalRTC:        m["LocalRTC"] == "yes",
		NTP:             m["NTP"] == "yes",
		NTPSynchronized: m["NTPSynchronized"] == "yes",
		CanNTP:          m["CanNTP"] == "yes",
		Time:            time.Now().UTC().Format(time.RFC3339),
	}, nil
}

func registerTimedate(api huma.API) {
	huma.Register(api, huma.Operation{
		OperationID: "system-get-time",
		Method:      "GET",
		Path:        "/api/system/time",
		Summary:     "Get time, timezone and sync status",
		Description: "Returns the current UTC time plus timezone and " +
			"synchronization state from timedatectl.",
		Tags:     []string{tagSystem},
		Metadata: op("read"),
		Errors:   readErrors,
	}, func(ctx context.Context, _ *struct{}) (*GetTimeOutput, error) {
		body, err := readTimeStatus()
		if err != nil {
			return nil, huma.Error500InternalServerError("timedatectl failed", err)
		}
		return &GetTimeOutput{Body: body}, nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "system-list-timezones",
		Method:      "GET",
		Path:        "/api/system/timezones",
		Summary:     "List available timezones",
		Description: "Returns every IANA timezone name known to the host, " +
			"suitable for populating a selector.",
		Tags:     []string{tagSystem},
		Metadata: op("read"),
		Errors:   readErrors,
	}, func(ctx context.Context, _ *struct{}) (*TimezonesOutput, error) {
		zones, err := oscmd.RunLines("timedatectl", "list-timezones")
		if err != nil {
			return nil, huma.Error500InternalServerError("timedatectl failed", err)
		}
		out := &TimezonesOutput{}
		out.Body.Timezones = zones
		return out, nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "system-set-timezone",
		Method:      "POST",
		Path:        "/api/system/timezone",
		Summary:     "Set system timezone",
		Description: "Sets the timezone via timedatectl. The value is validated " +
			"against the host's timezone list, so an unknown name returns 400.",
		Tags:     []string{tagSystem},
		Metadata: op("write"),
		Errors:   writeErrors,
	}, func(ctx context.Context, in *SetTimezoneInput) (*oscmd.StatusOutput, error) {
		tz := strings.TrimSpace(in.Body.Timezone)
		if tz == "" {
			return nil, huma.Error400BadRequest("empty timezone")
		}
		zones, err := oscmd.RunLines("timedatectl", "list-timezones")
		if err != nil {
			return nil, huma.Error500InternalServerError("timedatectl failed", err)
		}
		if !slices.Contains(zones, tz) {
			return nil, huma.Error400BadRequest("unknown timezone: " + tz)
		}
		if _, err := oscmd.Run("timedatectl", "set-timezone", tz); err != nil {
			return nil, huma.Error500InternalServerError("set-timezone failed", err)
		}
		return oscmd.OK(), nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "system-set-ntp",
		Method:      "POST",
		Path:        "/api/system/ntp",
		Summary:     "Enable or disable time synchronization",
		Description: "Toggles network time synchronization via " +
			"`timedatectl set-ntp`. Selecting specific NTP servers is not yet " +
			"supported. Returns the resulting time status: on enable, `ntp` is " +
			"true immediately, but `ntp_synchronized` stays false until the NTP " +
			"daemon converges (seconds to minutes).",
		Tags:     []string{tagSystem},
		Metadata: op("write"),
		Errors:   []int{400, 401, 403, 409, 500},
	}, func(ctx context.Context, in *SetNTPInput) (*GetTimeOutput, error) {
		// Enabling is a no-op when no NTP daemon is installed (CanNTP=no):
		// timedatectl reports success but nothing syncs. Reject it clearly.
		if in.Body.Enabled {
			if m, err := timedatectlShow(); err == nil && m["CanNTP"] != "yes" {
				return nil, huma.Error409Conflict("cannot enable NTP: no NTP service available on this host")
			}
		}
		val := strconv.FormatBool(in.Body.Enabled)
		if _, err := oscmd.Run("timedatectl", "set-ntp", val); err != nil {
			return nil, huma.Error500InternalServerError("set-ntp failed", err)
		}
		body, err := readTimeStatus()
		if err != nil {
			return nil, huma.Error500InternalServerError("set-ntp succeeded but reading status failed", err)
		}
		return &GetTimeOutput{Body: body}, nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "system-set-time",
		Method:      "POST",
		Path:        "/api/system/time",
		Summary:     "Set system time manually",
		Description: "Sets the clock to an explicit RFC3339 time. This only works " +
			"when NTP is disabled; otherwise timedatectl refuses and the call " +
			"returns 409.",
		Tags:     []string{tagSystem},
		Metadata: op("write"),
		Errors:   []int{400, 401, 403, 409, 500},
	}, func(ctx context.Context, in *SetTimeInput) (*oscmd.StatusOutput, error) {
		t, err := time.Parse(time.RFC3339, strings.TrimSpace(in.Body.Time))
		if err != nil {
			return nil, huma.Error400BadRequest("time must be RFC3339", err)
		}
		// timedatectl set-time interprets its argument as local wall-clock time.
		stamp := t.Local().Format("2006-01-02 15:04:05")
		if _, err := oscmd.Run("timedatectl", "set-time", stamp); err != nil {
			// timedatectl refuses while NTP is active; make that actionable.
			return nil, huma.Error409Conflict("set-time failed (disable NTP first?)", err)
		}
		return oscmd.OK(), nil
	})
}
