package keybase

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

type UCANResult struct {
	ID           int64           `json:"id"`
	CID          string          `json:"cid"`
	Issuer       string          `json:"issuer"`
	Audience     string          `json:"audience"`
	Subject      string          `json:"subject,omitempty"`
	Capabilities json.RawMessage `json:"capabilities"`
	NotBefore    string          `json:"not_before,omitempty"`
	ExpiresAt    string          `json:"expires_at"`
	IsRevoked    bool            `json:"is_revoked"`
	CreatedAt    string          `json:"created_at"`
}

type NewUCANInput struct {
	CID          string          `json:"cid"`
	Issuer       string          `json:"issuer"`
	Audience     string          `json:"audience"`
	Subject      string          `json:"subject,omitempty"`
	Capabilities json.RawMessage `json:"capabilities"`
	ProofChain   json.RawMessage `json:"proof_chain,omitempty"`
	NotBefore    string          `json:"not_before,omitempty"`
	ExpiresAt    string          `json:"expires_at"`
	Nonce        string          `json:"nonce,omitempty"`
	Facts        json.RawMessage `json:"facts,omitempty"`
	Signature    string          `json:"signature"`
	RawToken     string          `json:"raw_token"`
}

func (am *ActionManager) CreateUCAN(ctx context.Context, params NewUCANInput) (*UCANResult, error) {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

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

	var subject, notBefore, nonce *string
	if params.Subject != "" {
		subject = &params.Subject
	}
	if params.NotBefore != "" {
		notBefore = &params.NotBefore
	}
	if params.Nonce != "" {
		nonce = &params.Nonce
	}

	proofChain := params.ProofChain
	if proofChain == nil {
		proofChain = json.RawMessage(`[]`)
	}
	facts := params.Facts
	if facts == nil {
		facts = json.RawMessage(`{}`)
	}

	ucan, err := am.kb.queries.CreateUCAN(ctx, CreateUCANParams{
		DidID:        am.kb.didID,
		Cid:          params.CID,
		Issuer:       params.Issuer,
		Audience:     params.Audience,
		Subject:      subject,
		Capabilities: params.Capabilities,
		ProofChain:   proofChain,
		NotBefore:    notBefore,
		ExpiresAt:    params.ExpiresAt,
		Nonce:        nonce,
		Facts:        facts,
		Signature:    params.Signature,
		RawToken:     params.RawToken,
	})
	if err != nil {
		return nil, fmt.Errorf("create ucan: %w", err)
	}

	return ucanToResult(&ucan), nil
}

func (am *ActionManager) ListUCANs(ctx context.Context) ([]UCANResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

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

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

	results := make([]UCANResult, len(ucans))
	for i, u := range ucans {
		results[i] = *ucanToResult(&u)
	}

	return results, nil
}

func (am *ActionManager) GetUCANByCID(ctx context.Context, cid string) (*UCANResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

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

	return ucanToResult(&ucan), nil
}

func (am *ActionManager) ListUCANsByAudience(ctx context.Context, audience string) ([]UCANResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

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

	results := make([]UCANResult, len(ucans))
	for i, u := range ucans {
		results[i] = *ucanToResult(&u)
	}

	return results, nil
}

func (am *ActionManager) RevokeUCAN(ctx context.Context, cid string) error {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

	return am.kb.queries.RevokeUCAN(ctx, cid)
}

func (am *ActionManager) IsUCANRevoked(ctx context.Context, cid string) (bool, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	revoked, err := am.kb.queries.IsUCANRevoked(ctx, cid)
	if err != nil {
		return false, fmt.Errorf("check ucan revocation: %w", err)
	}

	return revoked == 1, nil
}

func (am *ActionManager) CreateRevocation(ctx context.Context, ucanCID string, revokedBy string, reason string) error {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

	var reasonPtr *string
	if reason != "" {
		reasonPtr = &reason
	}

	if err := am.kb.queries.RevokeUCAN(ctx, ucanCID); err != nil {
		return fmt.Errorf("revoke ucan token: %w", err)
	}

	return am.kb.queries.CreateRevocation(ctx, CreateRevocationParams{
		UcanCid:   ucanCID,
		RevokedBy: revokedBy,
		Reason:    reasonPtr,
	})
}

func (am *ActionManager) CleanExpiredUCANs(ctx context.Context) error {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

	return am.kb.queries.CleanExpiredUCANs(ctx)
}

func ucanToResult(u *UcanToken) *UCANResult {
	subject := ""
	if u.Subject != nil {
		subject = *u.Subject
	}
	notBefore := ""
	if u.NotBefore != nil {
		notBefore = *u.NotBefore
	}

	return &UCANResult{
		ID:           u.ID,
		CID:          u.Cid,
		Issuer:       u.Issuer,
		Audience:     u.Audience,
		Subject:      subject,
		Capabilities: u.Capabilities,
		NotBefore:    notBefore,
		ExpiresAt:    u.ExpiresAt,
		IsRevoked:    u.IsRevoked == 1,
		CreatedAt:    u.CreatedAt,
	}
}
