// plexum-kimi-provider-plugin is a Plexum ProviderPlugin that serves
// Moonshot Kimi K2.6 ("Kimi for Coding") via Kimi's Anthropic-compatible
// coding endpoint (https://api.kimi.com/coding/v1/messages).
//
// Reference: openclaw/extensions/kimi-coding/provider-catalog.ts —
// same base URL + User-Agent convention. The endpoint accepts an
// `sk-kimi-` subscription API key minted from kimi.com/code.
//
// Declared models:
//   - kimi-for-coding         (current; default)
//   - kimi-code               (legacy alias; same model server-side)
//   - k2p5                    (legacy alias; same)
//
// Operator agent.json.model = "kimi-for-coding" routes here via
// Plexum's ProviderRouter.
package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"git.hangman-lab.top/hzhang/Plexum-sdk-go/canonical"
	plugin "git.hangman-lab.top/hzhang/Plexum-sdk-go/plugin"

	"git.hangman-lab.top/hzhang/Plexum-anthropic-compat-client/anthropic"
	"git.hangman-lab.top/hzhang/Plexum-anthropic-compat-client/translate"
)

const (
	pluginName = "plexum-kimi-provider"

	// Kimi coding endpoint. The path /v1/messages is appended by the
	// anthropic client; baseURL must NOT include /v1.
	defaultBaseURL = "https://api.kimi.com/coding"

	// UA that openclaw's kimi-coding plugin sets. Likely a server-side
	// allowlist marker for the coding subscription auth path; omitting
	// it currently still works but we send it for parity.
	defaultUserAgent = "claude-code/0.1.0"

	// Kimi K2.6 supports up to 32768 output tokens per request.
	defaultMaxTokens = 8192
)

// supportedModels = what manifest.contracts.provider.models advertises.
// The legacy aliases (kimi-code, k2p5) route the same way; Kimi server
// accepts them and returns kimi-for-coding-equivalent output.
var supportedModels = []string{"kimi-for-coding", "kimi-code", "k2p5"}

// HostConfig is per-profile plugin config at
// <profile>/plugins/plexum-kimi-provider/config.json:
//
//	{
//	  "api_key":  "sk-kimi-...",       // required
//	  "base_url": "https://...",       // optional override
//	  "user_agent": "...",             // optional; default "claude-code/0.1.0"
//	  "max_tokens_default": 8192       // optional
//	}
type HostConfig struct {
	APIKey           string `json:"api_key"`
	BaseURL          string `json:"base_url"`
	UserAgent        string `json:"user_agent"`
	MaxTokensDefault int    `json:"max_tokens_default"`
}

type kimiPlugin struct {
	host plugin.HostAPI
	cfg  HostConfig
	cli  *anthropic.Client
}

func (p *kimiPlugin) Manifest() plugin.Manifest {
	return plugin.Manifest{
		Name:       pluginName,
		Version:    "0.1.0",
		Activation: plugin.ActivationLazy,
		Executable: "plexum-kimi-provider-plugin",
		Contracts: plugin.Contracts{
			Provider: &plugin.ProviderContract{Models: supportedModels},
		},
	}
}

func (p *kimiPlugin) Init(ctx context.Context, host plugin.HostAPI) error {
	p.host = host

	profileRoot := os.Getenv("PLEXUM_PROFILE_ROOT")
	if profileRoot == "" {
		home, _ := os.UserHomeDir()
		profileRoot = filepath.Join(home, ".plexum")
	}
	cfgPath := filepath.Join(profileRoot, "plugins", pluginName, "config.json")
	raw, err := os.ReadFile(cfgPath)
	if err != nil && !errors.Is(err, os.ErrNotExist) {
		return fmt.Errorf("read %s: %w", cfgPath, err)
	}
	if len(raw) > 0 {
		if err := json.Unmarshal(raw, &p.cfg); err != nil {
			return fmt.Errorf("parse %s: %w", cfgPath, err)
		}
	}
	if p.cfg.APIKey == "" {
		return fmt.Errorf("kimi: api_key missing in %s", cfgPath)
	}
	base := p.cfg.BaseURL
	if base == "" {
		base = defaultBaseURL
	}
	ua := p.cfg.UserAgent
	if ua == "" {
		ua = defaultUserAgent
	}
	if p.cfg.MaxTokensDefault <= 0 {
		p.cfg.MaxTokensDefault = defaultMaxTokens
	}
	p.cli = anthropic.New(base, p.cfg.APIKey)
	p.cli.UserAgent = ua
	host.Log("info", "kimi provider initialized", map[string]any{
		"base": base, "user_agent": ua, "models": supportedModels,
		"max_tokens_default": p.cfg.MaxTokensDefault,
	})
	return nil
}

// Stream is the ProviderPlugin entrypoint. canonical.TurnRequest in,
// channel of canonical.TurnEvent out.
func (p *kimiPlugin) Stream(ctx context.Context, modelID string, req canonical.TurnRequest) (<-chan canonical.TurnEvent, error) {
	// Normalize legacy aliases to the current canonical id so the wire
	// model name is consistent (Kimi server accepts all 3, but this
	// keeps logs + telemetry tidy).
	wireModel := modelID
	if wireModel == "kimi-code" || wireModel == "k2p5" {
		wireModel = "kimi-for-coding"
	}
	apiReq, err := translate.CanonicalToAnthropic(req, wireModel, p.cfg.MaxTokensDefault)
	if err != nil {
		return nil, err
	}
	raw, err := p.cli.StreamMessages(ctx, apiReq)
	if err != nil {
		return nil, err
	}
	out := make(chan canonical.TurnEvent, 32)
	go func() {
		defer close(out)
		tr := translate.NewTranslator()
		for ev := range raw {
			for _, te := range tr.Translate(ev) {
				select {
				case out <- te:
				case <-ctx.Done():
					return
				}
			}
		}
	}()
	return out, nil
}

func main() {
	if err := plugin.Serve(&kimiPlugin{}); err != nil {
		fmt.Fprintf(os.Stderr, "plexum-kimi-provider-plugin: %v\n", err)
		os.Exit(1)
	}
}
