package keybase

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

type DelegationResult struct {
	ID        int64           `json:"id"`
	UcanID    int64           `json:"ucan_id"`
	Delegator string          `json:"delegator"`
	Delegate  string          `json:"delegate"`
	Resource  string          `json:"resource"`
	Action    string          `json:"action"`
	Caveats   json.RawMessage `json:"caveats"`
	Depth     int64           `json:"depth"`
	Status    string          `json:"status"`
	CreatedAt string          `json:"created_at"`
	ExpiresAt string          `json:"expires_at,omitempty"`
}

type NewDelegationInput struct {
	UcanID    int64           `json:"ucan_id"`
	Delegator string          `json:"delegator"`
	Delegate  string          `json:"delegate"`
	Resource  string          `json:"resource"`
	Action    string          `json:"action"`
	Caveats   json.RawMessage `json:"caveats,omitempty"`
	ParentID  int64           `json:"parent_id,omitempty"`
	Depth     int64           `json:"depth"`
	ExpiresAt string          `json:"expires_at,omitempty"`
}

func (am *ActionManager) CreateDelegation(ctx context.Context, params NewDelegationInput) (*DelegationResult, error) {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

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

	var parentID *int64
	if params.ParentID != 0 {
		parentID = &params.ParentID
	}

	var expiresAt *string
	if params.ExpiresAt != "" {
		expiresAt = &params.ExpiresAt
	}

	caveats := params.Caveats
	if caveats == nil {
		caveats = json.RawMessage(`{}`)
	}

	d, err := am.kb.queries.CreateDelegation(ctx, CreateDelegationParams{
		DidID:     am.kb.didID,
		UcanID:    params.UcanID,
		Delegator: params.Delegator,
		Delegate:  params.Delegate,
		Resource:  params.Resource,
		Action:    params.Action,
		Caveats:   caveats,
		ParentID:  parentID,
		Depth:     params.Depth,
		ExpiresAt: expiresAt,
	})
	if err != nil {
		return nil, fmt.Errorf("create delegation: %w", err)
	}

	return delegationToResult(&d), nil
}

func (am *ActionManager) ListDelegationsByDelegator(ctx context.Context, delegator string) ([]DelegationResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	delegations, err := am.kb.queries.ListDelegationsByDelegator(ctx, delegator)
	if err != nil {
		return nil, fmt.Errorf("list delegations by delegator: %w", err)
	}

	results := make([]DelegationResult, len(delegations))
	for i, d := range delegations {
		results[i] = *delegationToResult(&d)
	}

	return results, nil
}

func (am *ActionManager) ListDelegationsByDelegate(ctx context.Context, delegate string) ([]DelegationResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	delegations, err := am.kb.queries.ListDelegationsByDelegate(ctx, delegate)
	if err != nil {
		return nil, fmt.Errorf("list delegations by delegate: %w", err)
	}

	results := make([]DelegationResult, len(delegations))
	for i, d := range delegations {
		results[i] = *delegationToResult(&d)
	}

	return results, nil
}

func (am *ActionManager) ListDelegationsForResource(ctx context.Context, resource string) ([]DelegationResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

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

	delegations, err := am.kb.queries.ListDelegationsForResource(ctx, ListDelegationsForResourceParams{
		DidID:    am.kb.didID,
		Resource: resource,
	})
	if err != nil {
		return nil, fmt.Errorf("list delegations for resource: %w", err)
	}

	results := make([]DelegationResult, len(delegations))
	for i, d := range delegations {
		results[i] = *delegationToResult(&d)
	}

	return results, nil
}

func (am *ActionManager) GetDelegationChain(ctx context.Context, delegationID int64) ([]DelegationResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	delegations, err := am.kb.queries.GetDelegationChain(ctx, GetDelegationChainParams{
		ID:       delegationID,
		ParentID: &delegationID,
	})
	if err != nil {
		return nil, fmt.Errorf("get delegation chain: %w", err)
	}

	results := make([]DelegationResult, len(delegations))
	for i, d := range delegations {
		results[i] = *delegationToResult(&d)
	}

	return results, nil
}

func (am *ActionManager) RevokeDelegation(ctx context.Context, delegationID int64) error {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

	return am.kb.queries.RevokeDelegation(ctx, delegationID)
}

func delegationToResult(d *Delegation) *DelegationResult {
	expiresAt := ""
	if d.ExpiresAt != nil {
		expiresAt = *d.ExpiresAt
	}

	return &DelegationResult{
		ID:        d.ID,
		UcanID:    d.UcanID,
		Delegator: d.Delegator,
		Delegate:  d.Delegate,
		Resource:  d.Resource,
		Action:    d.Action,
		Caveats:   d.Caveats,
		Depth:     d.Depth,
		Status:    d.Status,
		CreatedAt: d.CreatedAt,
		ExpiresAt: expiresAt,
	}
}
