package ocpp

import (
	"context"
	"encoding/json"
	"errors"
)

// This file is the outbound control surface: typed helpers that build an OCPP
// CALL, send it to the charger via Session.Call, and parse the reply. They work
// identically in own and proxy modes (both inject over the same socket).

// StatusResponse is the common {status: "..."} reply most control calls return
// (e.g. "Accepted", "Rejected", "Scheduled", "NotSupported").
type StatusResponse struct {
	Status string `json:"status"`
}

// statusCall issues a control CALL that replies with a {status} object.
func (s *Session) statusCall(ctx context.Context, action string, payload any) (string, error) {
	raw, err := s.Call(ctx, action, payload)
	if err != nil {
		return "", err
	}
	var r StatusResponse
	if err := json.Unmarshal(raw, &r); err != nil {
		return "", err
	}
	return r.Status, nil
}

// RemoteStartTransaction asks the charger to begin a charging session. A blank
// idTag is defaulted; connectorID <= 0 lets the charger choose.
func (s *Session) RemoteStartTransaction(ctx context.Context, idTag string, connectorID int) (string, error) {
	if idTag == "" {
		idTag = "DriverVault"
	}
	payload := map[string]any{"idTag": idTag}
	if connectorID > 0 {
		payload["connectorId"] = connectorID
	}
	return s.statusCall(ctx, "RemoteStartTransaction", payload)
}

// RemoteStopTransaction stops a session. When transactionID <= 0 the id from the
// live status snapshot (own mode) is used.
func (s *Session) RemoteStopTransaction(ctx context.Context, transactionID int) (string, error) {
	if transactionID <= 0 {
		transactionID = s.Snapshot().TransactionID
	}
	if transactionID <= 0 {
		return "", errors.New("ocpp: no active transaction to stop")
	}
	return s.statusCall(ctx, "RemoteStopTransaction", map[string]any{"transactionId": transactionID})
}

// SetCurrentLimit caps the charge current (Amperes) via a TxDefaultProfile. A
// connectorID <= 0 defaults to connector 1.
func (s *Session) SetCurrentLimit(ctx context.Context, connectorID int, amps float64) (string, error) {
	if connectorID <= 0 {
		connectorID = 1
	}
	profile := map[string]any{
		"chargingProfileId":      1,
		"stackLevel":             0,
		"chargingProfilePurpose": "TxDefaultProfile",
		"chargingProfileKind":    "Relative",
		"chargingSchedule": map[string]any{
			"chargingRateUnit": "A",
			"chargingSchedulePeriod": []any{
				map[string]any{"startPeriod": 0, "limit": amps},
			},
		},
	}
	return s.statusCall(ctx, "SetChargingProfile", map[string]any{
		"connectorId":        connectorID,
		"csChargingProfiles": profile,
	})
}

// ClearChargingProfile removes charging profiles (lifting a current limit). A
// connectorID <= 0 clears all connectors.
func (s *Session) ClearChargingProfile(ctx context.Context, connectorID int) (string, error) {
	payload := map[string]any{}
	if connectorID > 0 {
		payload["connectorId"] = connectorID
	}
	return s.statusCall(ctx, "ClearChargingProfile", payload)
}

// ChangeAvailability sets a connector Operative/Inoperative. connectorID 0
// targets the whole charge point.
func (s *Session) ChangeAvailability(ctx context.Context, connectorID int, operative bool) (string, error) {
	typ := "Inoperative"
	if operative {
		typ = "Operative"
	}
	return s.statusCall(ctx, "ChangeAvailability", map[string]any{
		"connectorId": connectorID,
		"type":        typ,
	})
}

// Reset reboots the charger (Soft or Hard).
func (s *Session) Reset(ctx context.Context, hard bool) (string, error) {
	typ := "Soft"
	if hard {
		typ = "Hard"
	}
	return s.statusCall(ctx, "Reset", map[string]any{"type": typ})
}

// UnlockConnector releases the cable lock on a connector.
func (s *Session) UnlockConnector(ctx context.Context, connectorID int) (string, error) {
	if connectorID <= 0 {
		connectorID = 1
	}
	return s.statusCall(ctx, "UnlockConnector", map[string]any{"connectorId": connectorID})
}

// TriggerMessage asks the charger to proactively send a message (e.g.
// "StatusNotification", "MeterValues", "BootNotification", "Heartbeat").
func (s *Session) TriggerMessage(ctx context.Context, requestedMessage string, connectorID int) (string, error) {
	payload := map[string]any{"requestedMessage": requestedMessage}
	if connectorID > 0 {
		payload["connectorId"] = connectorID
	}
	return s.statusCall(ctx, "TriggerMessage", payload)
}

// ChangeConfiguration sets a charger configuration key.
func (s *Session) ChangeConfiguration(ctx context.Context, key, value string) (string, error) {
	return s.statusCall(ctx, "ChangeConfiguration", map[string]any{"key": key, "value": value})
}

// ConfigKey is one entry in a GetConfiguration reply.
type ConfigKey struct {
	Key      string `json:"key"`
	Readonly bool   `json:"readonly"`
	Value    string `json:"value,omitempty"`
}

// GetConfigurationResult is the GetConfiguration reply.
type GetConfigurationResult struct {
	ConfigurationKey []ConfigKey `json:"configurationKey"`
	UnknownKey       []string    `json:"unknownKey,omitempty"`
}

// GetConfiguration reads charger configuration keys; nil/empty keys returns all.
func (s *Session) GetConfiguration(ctx context.Context, keys []string) (GetConfigurationResult, error) {
	payload := map[string]any{}
	if len(keys) > 0 {
		payload["key"] = keys
	}
	raw, err := s.Call(ctx, "GetConfiguration", payload)
	if err != nil {
		return GetConfigurationResult{}, err
	}
	var r GetConfigurationResult
	if err := json.Unmarshal(raw, &r); err != nil {
		return GetConfigurationResult{}, err
	}
	return r, nil
}
