package keybase

import (
	"context"
	"fmt"
)

// =============================================================================
// INVOCATION ACTIONS (UCAN v1.0.0-rc.1)
// =============================================================================

// InvocationResult represents an invocation in API responses.
type InvocationResult struct {
	ID         int64  `json:"id"`
	CID        string `json:"cid"`
	Issuer     string `json:"iss"`
	Subject    string `json:"sub"`
	Audience   string `json:"aud,omitempty"`
	Command    string `json:"cmd"`
	Proofs     string `json:"prf"`
	Expiration string `json:"exp,omitempty"`
	IssuedAt   string `json:"iat,omitempty"`
	ExecutedAt string `json:"executed_at,omitempty"`
	ResultCID  string `json:"result_cid,omitempty"`
	CreatedAt  string `json:"created_at"`
}

// StoreInvocationParams contains parameters for storing an invocation.
type StoreInvocationParams struct {
	CID        string `json:"cid"`
	Envelope   []byte `json:"envelope"`
	Issuer     string `json:"iss"`
	Subject    string `json:"sub"`
	Audience   string `json:"aud,omitempty"`
	Command    string `json:"cmd"`
	Proofs     string `json:"prf"`
	Expiration string `json:"exp,omitempty"`
	IssuedAt   string `json:"iat,omitempty"`
}

// StoreInvocation stores a new UCAN invocation envelope.
func (am *ActionManager) StoreInvocation(ctx context.Context, params StoreInvocationParams) (*InvocationResult, error) {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

	if am.kb.didID == 0 {
		return nil, fmt.Errorf("DID not initialized")
	}

	var aud, exp, iat *string
	if params.Audience != "" {
		aud = &params.Audience
	}
	if params.Expiration != "" {
		exp = &params.Expiration
	}
	if params.IssuedAt != "" {
		iat = &params.IssuedAt
	}

	inv, err := am.kb.queries.CreateInvocation(ctx, CreateInvocationParams{
		DidID:    am.kb.didID,
		Cid:      params.CID,
		Envelope: params.Envelope,
		Iss:      params.Issuer,
		Sub:      params.Subject,
		Aud:      aud,
		Cmd:      params.Command,
		Prf:      params.Proofs,
		Exp:      exp,
		Iat:      iat,
	})
	if err != nil {
		return nil, fmt.Errorf("create invocation: %w", err)
	}

	return invocationToResult(inv), nil
}

// GetInvocationByCID retrieves an invocation by its CID.
func (am *ActionManager) GetInvocationByCID(ctx context.Context, cid string) (*InvocationResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	inv, err := am.kb.queries.GetInvocationByCID(ctx, cid)
	if err != nil {
		return nil, fmt.Errorf("get invocation: %w", err)
	}

	return invocationToResult(inv), nil
}

// GetInvocationEnvelope retrieves the raw CBOR envelope for an invocation.
func (am *ActionManager) GetInvocationEnvelope(ctx context.Context, cid string) ([]byte, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	envelope, err := am.kb.queries.GetInvocationEnvelopeByCID(ctx, cid)
	if err != nil {
		return nil, fmt.Errorf("get invocation envelope: %w", err)
	}

	return envelope, nil
}

// ListInvocations returns recent invocations for the current DID.
func (am *ActionManager) ListInvocations(ctx context.Context, limit int64) ([]InvocationResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	if am.kb.didID == 0 {
		return []InvocationResult{}, nil
	}

	if limit <= 0 {
		limit = 50
	}

	invocations, err := am.kb.queries.ListInvocationsByDID(ctx, ListInvocationsByDIDParams{
		DidID: am.kb.didID,
		Limit: limit,
	})
	if err != nil {
		return nil, fmt.Errorf("list invocations: %w", err)
	}

	results := make([]InvocationResult, len(invocations))
	for i, inv := range invocations {
		results[i] = *invocationToResult(inv)
	}

	return results, nil
}

// ListInvocationsByCommand returns invocations for a specific command.
func (am *ActionManager) ListInvocationsByCommand(ctx context.Context, cmd string, limit int64) ([]InvocationResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	if am.kb.didID == 0 {
		return []InvocationResult{}, nil
	}

	if limit <= 0 {
		limit = 50
	}

	invocations, err := am.kb.queries.ListInvocationsForCommand(ctx, ListInvocationsForCommandParams{
		DidID: am.kb.didID,
		Cmd:   cmd,
		Limit: limit,
	})
	if err != nil {
		return nil, fmt.Errorf("list invocations for command: %w", err)
	}

	results := make([]InvocationResult, len(invocations))
	for i, inv := range invocations {
		results[i] = *invocationToResult(inv)
	}

	return results, nil
}

// ListPendingInvocations returns invocations that haven't been executed yet.
func (am *ActionManager) ListPendingInvocations(ctx context.Context) ([]InvocationResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	if am.kb.didID == 0 {
		return []InvocationResult{}, nil
	}

	invocations, err := am.kb.queries.ListPendingInvocations(ctx, am.kb.didID)
	if err != nil {
		return nil, fmt.Errorf("list pending invocations: %w", err)
	}

	results := make([]InvocationResult, len(invocations))
	for i, inv := range invocations {
		results[i] = *invocationToResult(inv)
	}

	return results, nil
}

// MarkInvocationExecuted marks an invocation as executed with an optional result CID.
func (am *ActionManager) MarkInvocationExecuted(ctx context.Context, cid string, resultCID string) error {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

	var result *string
	if resultCID != "" {
		result = &resultCID
	}

	err := am.kb.queries.MarkInvocationExecuted(ctx, MarkInvocationExecutedParams{
		ResultCid: result,
		Cid:       cid,
	})
	if err != nil {
		return fmt.Errorf("mark invocation executed: %w", err)
	}

	return nil
}

// CleanOldInvocations removes invocations older than 90 days.
func (am *ActionManager) CleanOldInvocations(ctx context.Context) error {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

	if err := am.kb.queries.CleanOldInvocations(ctx); err != nil {
		return fmt.Errorf("clean old invocations: %w", err)
	}

	return nil
}

// invocationToResult converts a UcanInvocation to InvocationResult.
func invocationToResult(inv UcanInvocation) *InvocationResult {
	audience := ""
	if inv.Aud != nil {
		audience = *inv.Aud
	}
	expiration := ""
	if inv.Exp != nil {
		expiration = *inv.Exp
	}
	issuedAt := ""
	if inv.Iat != nil {
		issuedAt = *inv.Iat
	}
	executedAt := ""
	if inv.ExecutedAt != nil {
		executedAt = *inv.ExecutedAt
	}
	resultCID := ""
	if inv.ResultCid != nil {
		resultCID = *inv.ResultCid
	}

	return &InvocationResult{
		ID:         inv.ID,
		CID:        inv.Cid,
		Issuer:     inv.Iss,
		Subject:    inv.Sub,
		Audience:   audience,
		Command:    inv.Cmd,
		Proofs:     inv.Prf,
		Expiration: expiration,
		IssuedAt:   issuedAt,
		ExecutedAt: executedAt,
		ResultCID:  resultCID,
		CreatedAt:  inv.CreatedAt,
	}
}
