package networking

import (
	"context"
	"errors"

	"nadir/internal/oscmd"

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

// registerWrites adds all write endpoints for the networking module. Every
// handler checks m.be != nil first and returns 501 when no backend was detected.

type ApplyInput struct {
	Name string      `path:"name" example:"eth0" doc:"Interface name"`
	Body IfaceConfig `doc:"Desired interface configuration"`
}

type ApplyOutput struct {
	Body struct {
		Status          string `json:"status" example:"pending" doc:"Always \"pending\" — confirm to make permanent"`
		Interface       string `json:"interface" example:"eth0"`
		Backend         string `json:"backend" example:"nmcli" doc:"Network backend that applied the change"`
		RollbackSeconds int    `json:"rollback_seconds" example:"60" doc:"Seconds until auto-rollback unless confirmed"`
	}
}

type IfacePathInput struct {
	Name string `path:"name" example:"eth0" doc:"Interface name"`
}

type PendingOutput struct {
	Body PendingInfo
}

func registerWrites(api huma.API, m *Module) {
	huma.Register(api, huma.Operation{
		OperationID: "networking-apply-config",
		Method:      "PUT",
		Path:        "/api/networking/interfaces/{name}",
		Summary:     "Apply interface configuration",
		Description: "Replaces the interface's IPv4 configuration. The change is applied " +
			"immediately but starts a rollback timer — if not confirmed within the timeout " +
			"(default 60s), the prior configuration is automatically restored. This prevents " +
			"lock-yourself-out mistakes on remote hosts.",
		Tags:     []string{tagNetworking},
		Metadata: op("write"),
		Errors:   writeErrors,
	}, func(ctx context.Context, in *ApplyInput) (*ApplyOutput, error) {
		if m.be == nil {
			return nil, huma.Error501NotImplemented("", errNoBackend)
		}
		if err := validateIface(in.Name); err != nil {
			return nil, err
		}
		if err := in.Body.validate(); err != nil {
			return nil, err
		}

		seconds, err := m.startRollback(ctx, in.Name, in.Body)
		if err != nil {
			if errors.Is(err, errAlreadyPending) {
				return nil, huma.Error409Conflict(err.Error())
			}
			return nil, huma.Error500InternalServerError("apply failed", err)
		}

		out := &ApplyOutput{}
		out.Body.Status = "pending"
		out.Body.Interface = in.Name
		out.Body.Backend = m.be.Name()
		out.Body.RollbackSeconds = seconds
		return out, nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "networking-confirm-change",
		Method:      "POST",
		Path:        "/api/networking/interfaces/{name}/confirm",
		Summary:     "Confirm a pending change",
		Description: "Cancels the rollback timer, making the applied configuration permanent.",
		Tags:        []string{tagNetworking},
		Metadata:    op("write"),
		Errors:      writeErrors,
	}, func(ctx context.Context, in *IfacePathInput) (*oscmd.StatusOutput, error) {
		if m.be == nil {
			return nil, huma.Error501NotImplemented("", errNoBackend)
		}
		if err := validateIface(in.Name); err != nil {
			return nil, err
		}
		if err := m.confirm(in.Name); err != nil {
			return nil, huma.Error409Conflict(err.Error())
		}
		return oscmd.OK(), nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "networking-rollback-change",
		Method:      "POST",
		Path:        "/api/networking/interfaces/{name}/rollback",
		Summary:     "Immediately revert a pending change",
		Description: "Reverts the interface to its prior configuration and clears the pending change.",
		Tags:        []string{tagNetworking},
		Metadata:    op("write"),
		Errors:      writeErrors,
	}, func(ctx context.Context, in *IfacePathInput) (*oscmd.StatusOutput, error) {
		if m.be == nil {
			return nil, huma.Error501NotImplemented("", errNoBackend)
		}
		if err := validateIface(in.Name); err != nil {
			return nil, err
		}
		if err := m.rollbackNow(in.Name); err != nil {
			return nil, huma.Error500InternalServerError("rollback failed", err)
		}
		return oscmd.OK(), nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "networking-link-up",
		Method:      "POST",
		Path:        "/api/networking/interfaces/{name}/up",
		Summary:     "Bring an interface up",
		Tags:        []string{tagNetworking},
		Metadata:    op("write"),
		Errors:      writeErrors,
	}, func(ctx context.Context, in *IfacePathInput) (*oscmd.StatusOutput, error) {
		if m.be == nil {
			return nil, huma.Error501NotImplemented("", errNoBackend)
		}
		if err := validateIface(in.Name); err != nil {
			return nil, err
		}
		if err := m.be.SetLinkUp(ctx, in.Name); err != nil {
			return nil, huma.Error500InternalServerError("link up failed", err)
		}
		return oscmd.OK(), nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "networking-link-down",
		Method:      "POST",
		Path:        "/api/networking/interfaces/{name}/down",
		Summary:     "Take an interface down",
		Description: "Brings the interface down behind the rollback safety net: it is brought " +
			"back up automatically if not confirmed within the timeout (default 60s). This " +
			"prevents taking down the link you're managing the host over and losing access.",
		Tags:     []string{tagNetworking},
		Metadata: op("write"),
		Errors:   writeErrors,
	}, func(ctx context.Context, in *IfacePathInput) (*ApplyOutput, error) {
		if m.be == nil {
			return nil, huma.Error501NotImplemented("", errNoBackend)
		}
		if err := validateIface(in.Name); err != nil {
			return nil, err
		}
		seconds, err := m.startLinkDown(ctx, in.Name)
		if err != nil {
			if errors.Is(err, errAlreadyPending) {
				return nil, huma.Error409Conflict(err.Error())
			}
			return nil, huma.Error500InternalServerError("link down failed", err)
		}
		out := &ApplyOutput{}
		out.Body.Status = "pending"
		out.Body.Interface = in.Name
		out.Body.Backend = m.be.Name()
		out.Body.RollbackSeconds = seconds
		return out, nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "networking-get-pending",
		Method:      "GET",
		Path:        "/api/networking/pending",
		Summary:     "Get pending change status",
		Description: "Returns the currently pending change (interface name and seconds until " +
			"auto-rollback), or 404 if there is no pending change.",
		Tags:     []string{tagNetworking},
		Metadata: op("read"),
		Errors:   []int{401, 403, 404, 500},
	}, func(ctx context.Context, _ *struct{}) (*PendingOutput, error) {
		info := m.pendingInfo()
		if info == nil {
			return nil, huma.Error404NotFound("no pending change")
		}
		out := &PendingOutput{}
		out.Body = *info
		return out, nil
	})
}
