package jvm

import (
	"context"
	"net/http"
	"net/url"
	"time"

	"GoNavi-Wails/internal/connection"
)

type AgentProvider struct{}

func NewAgentProvider() Provider { return &AgentProvider{} }

func (p *AgentProvider) Mode() string { return ModeAgent }

func (p *AgentProvider) TestConnection(ctx context.Context, cfg connection.ConnectionConfig) error {
	runtime, err := newAgentRuntime(cfg)
	if err != nil {
		return err
	}

	resp, err := doContractProbe(ctx, runtime.contractRuntime, http.MethodHead)
	if err != nil {
		return err
	}
	if resp.StatusCode == http.StatusMethodNotAllowed || resp.StatusCode == http.StatusNotImplemented {
		_ = resp.Body.Close()
		resp, err = doContractProbe(ctx, runtime.contractRuntime, http.MethodGet)
		if err != nil {
			return err
		}
	}
	defer resp.Body.Close()
	if isReachableStatus(resp.StatusCode) {
		return nil
	}
	return buildContractStatusError("agent", "probe", resp)
}

func (p *AgentProvider) ProbeCapabilities(_ context.Context, cfg connection.ConnectionConfig) ([]Capability, error) {
	if _, err := newAgentRuntime(cfg); err != nil {
		return nil, err
	}
	readOnly := cfg.JVM.ReadOnly != nil && *cfg.JVM.ReadOnly
	return []Capability{{
		Mode:         ModeAgent,
		CanBrowse:    true,
		CanWrite:     !readOnly,
		CanPreview:   true,
		DisplayLabel: "Agent",
		Reason: func() string {
			if readOnly {
				return "当前连接只读"
			}
			return ""
		}(),
	}}, nil
}

func (p *AgentProvider) ListResources(ctx context.Context, cfg connection.ConnectionConfig, parentPath string) ([]ResourceSummary, error) {
	runtime, err := newAgentRuntime(cfg)
	if err != nil {
		return nil, err
	}

	query := url.Values{}
	query.Set("parentPath", parentPath)

	var resources []ResourceSummary
	if err := runtime.doJSON(ctx, http.MethodGet, "list resources", "resources", query, nil, &resources); err != nil {
		return nil, err
	}
	return resources, nil
}

func (p *AgentProvider) GetValue(ctx context.Context, cfg connection.ConnectionConfig, resourcePath string) (ValueSnapshot, error) {
	runtime, err := newAgentRuntime(cfg)
	if err != nil {
		return ValueSnapshot{}, err
	}

	query := url.Values{}
	query.Set("resourcePath", resourcePath)

	var snapshot ValueSnapshot
	if err := runtime.doJSON(ctx, http.MethodGet, "get value", "value", query, nil, &snapshot); err != nil {
		return ValueSnapshot{}, err
	}
	return snapshot, nil
}

func (p *AgentProvider) GetMonitoringSnapshot(ctx context.Context, cfg connection.ConnectionConfig, previous *JVMMonitoringPoint) (JVMMonitoringSnapshot, error) {
	runtime, err := newAgentRuntime(cfg)
	if err != nil {
		return JVMMonitoringSnapshot{}, err
	}

	var snapshot JVMMonitoringSnapshot
	if err := runtime.doJSON(ctx, http.MethodGet, "get monitoring snapshot", "metrics", nil, nil, &snapshot); err != nil {
		return JVMMonitoringSnapshot{}, err
	}
	finalizeMonitoringSnapshot(&snapshot, previous)
	return snapshot, nil
}

func (p *AgentProvider) PreviewChange(ctx context.Context, cfg connection.ConnectionConfig, req ChangeRequest) (ChangePreview, error) {
	runtime, err := newAgentRuntime(cfg)
	if err != nil {
		return ChangePreview{}, err
	}

	var preview ChangePreview
	if err := runtime.doJSON(ctx, http.MethodPost, "preview change", "preview", nil, req, &preview); err != nil {
		return ChangePreview{}, err
	}
	return preview, nil
}

func (p *AgentProvider) ApplyChange(ctx context.Context, cfg connection.ConnectionConfig, req ChangeRequest) (ApplyResult, error) {
	runtime, err := newAgentRuntime(cfg)
	if err != nil {
		return ApplyResult{}, err
	}

	var result ApplyResult
	if err := runtime.doJSON(ctx, http.MethodPost, "apply change", "apply", nil, req, &result); err != nil {
		return ApplyResult{}, err
	}
	return result, nil
}

type agentRuntime struct {
	contractRuntime
}

func newAgentRuntime(cfg connection.ConnectionConfig) (agentRuntime, error) {
	timeout := time.Duration(cfg.JVM.Agent.TimeoutSeconds) * time.Second
	if timeout <= 0 {
		timeout = time.Duration(cfg.Timeout) * time.Second
	}
	runtime, err := newContractRuntime(
		cfg.JVM.Agent.BaseURL,
		cfg.JVM.Agent.APIKey,
		timeout,
		"agent",
	)
	if err != nil {
		return agentRuntime{}, err
	}
	return agentRuntime{contractRuntime: runtime}, nil
}
