// Package keybase provides action handlers for concurrent database operations.
package keybase

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"time"
)

// ActionManager provides thread-safe database operations with concurrent read support.
type ActionManager struct {
	kb *Keybase
}

// NewActionManager creates an ActionManager from the current keybase instance.
func NewActionManager() (*ActionManager, error) {
	kb := Get()
	if kb == nil {
		return nil, fmt.Errorf("keybase not initialized")
	}
	return &ActionManager{kb: kb}, nil
}

// =============================================================================
// ACCOUNT ACTIONS
// =============================================================================

// AccountResult represents an account in API responses.
type AccountResult struct {
	ID           int64  `json:"id"`
	Address      string `json:"address"`
	ChainID      string `json:"chain_id"`
	CoinType     int64  `json:"coin_type"`
	AccountIndex int64  `json:"account_index"`
	AddressIndex int64  `json:"address_index"`
	Label        string `json:"label"`
	IsDefault    bool   `json:"is_default"`
	PublicKey    string `json:"public_key,omitempty"`
	Curve        string `json:"curve,omitempty"`
	CreatedAt    string `json:"created_at"`
}

// ListAccounts returns all accounts for the current DID.
func (am *ActionManager) ListAccounts(ctx context.Context) ([]AccountResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

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

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

	results := make([]AccountResult, len(rows))
	for i, row := range rows {
		label := ""
		if row.Label != nil {
			label = *row.Label
		}
		results[i] = AccountResult{
			ID:           row.ID,
			Address:      row.Address,
			ChainID:      row.ChainID,
			CoinType:     row.CoinType,
			AccountIndex: row.AccountIndex,
			AddressIndex: row.AddressIndex,
			Label:        label,
			IsDefault:    row.IsDefault == 1,
			PublicKey:    row.PublicKey,
			Curve:        row.Curve,
			CreatedAt:    row.CreatedAt,
		}
	}

	return results, nil
}

// GetAccountByAddress retrieves an account by its blockchain address.
func (am *ActionManager) GetAccountByAddress(ctx context.Context, address string) (*AccountResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	acc, err := am.kb.queries.GetAccountByAddress(ctx, address)
	if err != nil {
		return nil, fmt.Errorf("get account: %w", err)
	}

	label := ""
	if acc.Label != nil {
		label = *acc.Label
	}

	return &AccountResult{
		ID:           acc.ID,
		Address:      acc.Address,
		ChainID:      acc.ChainID,
		CoinType:     acc.CoinType,
		AccountIndex: acc.AccountIndex,
		AddressIndex: acc.AddressIndex,
		Label:        label,
		IsDefault:    acc.IsDefault == 1,
		CreatedAt:    acc.CreatedAt,
	}, nil
}

// =============================================================================
// CREDENTIAL ACTIONS
// =============================================================================

// CredentialResult represents a credential in API responses.
type CredentialResult struct {
	ID             int64    `json:"id"`
	CredentialID   string   `json:"credential_id"`
	DeviceName     string   `json:"device_name"`
	DeviceType     string   `json:"device_type"`
	Authenticator  string   `json:"authenticator"`
	Transports     []string `json:"transports"`
	SignCount      int64    `json:"sign_count"`
	IsDiscoverable bool     `json:"is_discoverable"`
	BackedUp       bool     `json:"backed_up"`
	CreatedAt      string   `json:"created_at"`
	LastUsed       string   `json:"last_used"`
}

// ListCredentials returns all credentials for the current DID.
func (am *ActionManager) ListCredentials(ctx context.Context) ([]CredentialResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

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

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

	results := make([]CredentialResult, len(creds))
	for i, cred := range creds {
		var transports []string
		if err := json.Unmarshal(cred.Transports, &transports); err != nil {
			transports = []string{}
		}

		authenticator := ""
		if cred.Authenticator != nil {
			authenticator = *cred.Authenticator
		}

		results[i] = CredentialResult{
			ID:             cred.ID,
			CredentialID:   cred.CredentialID,
			DeviceName:     cred.DeviceName,
			DeviceType:     cred.DeviceType,
			Authenticator:  authenticator,
			Transports:     transports,
			SignCount:      cred.SignCount,
			IsDiscoverable: cred.IsDiscoverable == 1,
			BackedUp:       cred.BackedUp == 1,
			CreatedAt:      cred.CreatedAt,
			LastUsed:       cred.LastUsed,
		}
	}

	return results, nil
}

// GetCredentialByID retrieves a credential by its WebAuthn credential ID.
func (am *ActionManager) GetCredentialByID(ctx context.Context, credentialID string) (*CredentialResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	cred, err := am.kb.queries.GetCredentialByID(ctx, credentialID)
	if err != nil {
		return nil, fmt.Errorf("get credential: %w", err)
	}

	var transports []string
	if err := json.Unmarshal(cred.Transports, &transports); err != nil {
		transports = []string{}
	}

	authenticator := ""
	if cred.Authenticator != nil {
		authenticator = *cred.Authenticator
	}

	return &CredentialResult{
		ID:             cred.ID,
		CredentialID:   cred.CredentialID,
		DeviceName:     cred.DeviceName,
		DeviceType:     cred.DeviceType,
		Authenticator:  authenticator,
		Transports:     transports,
		SignCount:      cred.SignCount,
		IsDiscoverable: cred.IsDiscoverable == 1,
		BackedUp:       cred.BackedUp == 1,
		CreatedAt:      cred.CreatedAt,
		LastUsed:       cred.LastUsed,
	}, nil
}

// =============================================================================
// SESSION ACTIONS
// =============================================================================

// SessionResult represents a session in API responses.
type SessionResult struct {
	ID            int64           `json:"id"`
	SessionID     string          `json:"session_id"`
	DeviceName    string          `json:"device_name"`
	Authenticator string          `json:"authenticator"`
	DeviceInfo    json.RawMessage `json:"device_info"`
	IsCurrent     bool            `json:"is_current"`
	LastActivity  string          `json:"last_activity"`
	ExpiresAt     string          `json:"expires_at"`
	CreatedAt     string          `json:"created_at"`
}

// ListSessions returns all active sessions for the current DID.
func (am *ActionManager) ListSessions(ctx context.Context) ([]SessionResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

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

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

	results := make([]SessionResult, len(sessions))
	for i, sess := range sessions {
		authenticator := ""
		if sess.Authenticator != nil {
			authenticator = *sess.Authenticator
		}

		results[i] = SessionResult{
			ID:            sess.ID,
			SessionID:     sess.SessionID,
			DeviceName:    sess.DeviceName,
			Authenticator: authenticator,
			DeviceInfo:    sess.DeviceInfo,
			IsCurrent:     sess.IsCurrent == 1,
			LastActivity:  sess.LastActivity,
			ExpiresAt:     sess.ExpiresAt,
			CreatedAt:     sess.CreatedAt,
		}
	}

	return results, nil
}

// NewSessionParams contains parameters for creating a session.
type NewSessionParams struct {
	CredentialID int64           `json:"credential_id"`
	DeviceInfo   json.RawMessage `json:"device_info"`
	ExpiresIn    time.Duration   `json:"expires_in"`
}

// CreateSession creates a new session for the current DID.
func (am *ActionManager) CreateSession(ctx context.Context, params NewSessionParams) (*SessionResult, error) {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

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

	sessionID := generateSessionID()
	expiresAt := time.Now().Add(params.ExpiresIn).UTC().Format(time.RFC3339)

	deviceInfo := params.DeviceInfo
	if deviceInfo == nil {
		deviceInfo = json.RawMessage(`{}`)
	}

	sess, err := am.kb.queries.CreateSession(ctx, CreateSessionParams{
		DidID:        am.kb.didID,
		CredentialID: params.CredentialID,
		SessionID:    sessionID,
		DeviceInfo:   deviceInfo,
		IsCurrent:    1,
		ExpiresAt:    expiresAt,
	})
	if err != nil {
		return nil, fmt.Errorf("create session: %w", err)
	}

	return &SessionResult{
		ID:           sess.ID,
		SessionID:    sess.SessionID,
		DeviceInfo:   sess.DeviceInfo,
		IsCurrent:    sess.IsCurrent == 1,
		LastActivity: sess.LastActivity,
		ExpiresAt:    sess.ExpiresAt,
		CreatedAt:    sess.CreatedAt,
	}, nil
}

// generateSessionID creates a random session identifier.
func generateSessionID() string {
	b := make([]byte, 16)
	rand.Read(b)
	return "sess_" + hex.EncodeToString(b)
}

// RevokeSession deletes a session by its ID.
func (am *ActionManager) RevokeSession(ctx context.Context, sessionID string) error {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

	sess, err := am.kb.queries.GetSessionByID(ctx, sessionID)
	if err != nil {
		return fmt.Errorf("get session: %w", err)
	}

	if err := am.kb.queries.DeleteSession(ctx, sess.ID); err != nil {
		return fmt.Errorf("delete session: %w", err)
	}

	return nil
}

// =============================================================================
// GRANT ACTIONS
// =============================================================================

// GrantResult represents a grant in API responses.
type GrantResult struct {
	ID            int64           `json:"id"`
	ServiceName   string          `json:"service_name"`
	ServiceOrigin string          `json:"service_origin"`
	ServiceLogo   string          `json:"service_logo,omitempty"`
	Scopes        json.RawMessage `json:"scopes"`
	Accounts      json.RawMessage `json:"accounts"`
	Status        string          `json:"status"`
	GrantedAt     string          `json:"granted_at"`
	LastUsed      string          `json:"last_used,omitempty"`
	ExpiresAt     string          `json:"expires_at,omitempty"`
}

// ListGrants returns all active grants for the current DID.
func (am *ActionManager) ListGrants(ctx context.Context) ([]GrantResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

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

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

	results := make([]GrantResult, len(grants))
	for i, g := range grants {
		serviceLogo := ""
		if g.ServiceLogo != nil {
			serviceLogo = *g.ServiceLogo
		}

		lastUsed := ""
		if g.LastUsed != nil {
			lastUsed = *g.LastUsed
		}

		expiresAt := ""
		if g.ExpiresAt != nil {
			expiresAt = *g.ExpiresAt
		}

		results[i] = GrantResult{
			ID:            g.ID,
			ServiceName:   g.ServiceName,
			ServiceOrigin: g.ServiceOrigin,
			ServiceLogo:   serviceLogo,
			Scopes:        g.Scopes,
			Accounts:      g.Accounts,
			Status:        g.Status,
			GrantedAt:     g.GrantedAt,
			LastUsed:      lastUsed,
			ExpiresAt:     expiresAt,
		}
	}

	return results, nil
}

// RevokeGrant revokes a grant by its ID.
func (am *ActionManager) RevokeGrant(ctx context.Context, grantID int64) error {
	am.kb.mu.Lock()
	defer am.kb.mu.Unlock()

	if err := am.kb.queries.RevokeGrant(ctx, grantID); err != nil {
		return fmt.Errorf("revoke grant: %w", err)
	}

	return nil
}

// =============================================================================
// DID RESOLUTION
// =============================================================================

// VerificationMethodResult represents a verification method in API responses.
type VerificationMethodResult struct {
	ID         string `json:"id"`
	Type       string `json:"type"`
	Controller string `json:"controller"`
	PublicKey  string `json:"public_key"`
	Purpose    string `json:"purpose"`
}

// DIDDocumentResult represents a resolved DID document.
type DIDDocumentResult struct {
	DID                 string                     `json:"did"`
	Controller          string                     `json:"controller"`
	VerificationMethods []VerificationMethodResult `json:"verification_methods"`
	Accounts            []AccountResult            `json:"accounts"`
	Credentials         []CredentialResult         `json:"credentials"`
}

// ResolveDID resolves a DID document with all associated data.
func (am *ActionManager) ResolveDID(ctx context.Context, did string) (*DIDDocumentResult, error) {
	am.kb.mu.RLock()
	defer am.kb.mu.RUnlock()

	// Get DID document
	doc, err := am.kb.queries.GetDIDByDID(ctx, did)
	if err != nil {
		return nil, fmt.Errorf("get DID document: %w", err)
	}

	// Get verification methods
	vms, err := am.kb.queries.ListVerificationMethods(ctx, doc.ID)
	if err != nil {
		return nil, fmt.Errorf("list verification methods: %w", err)
	}

	vmResults := make([]VerificationMethodResult, len(vms))
	for i, vm := range vms {
		vmResults[i] = VerificationMethodResult{
			ID:         vm.MethodID,
			Type:       vm.MethodType,
			Controller: vm.Controller,
			PublicKey:  vm.PublicKey,
			Purpose:    vm.Purpose,
		}
	}

	// Get accounts
	accountRows, err := am.kb.queries.ListAccountsByDID(ctx, doc.ID)
	if err != nil {
		return nil, fmt.Errorf("list accounts: %w", err)
	}

	accountResults := make([]AccountResult, len(accountRows))
	for i, row := range accountRows {
		label := ""
		if row.Label != nil {
			label = *row.Label
		}
		accountResults[i] = AccountResult{
			ID:           row.ID,
			Address:      row.Address,
			ChainID:      row.ChainID,
			CoinType:     row.CoinType,
			AccountIndex: row.AccountIndex,
			AddressIndex: row.AddressIndex,
			Label:        label,
			IsDefault:    row.IsDefault == 1,
			PublicKey:    row.PublicKey,
			Curve:        row.Curve,
			CreatedAt:    row.CreatedAt,
		}
	}

	// Get credentials
	creds, err := am.kb.queries.ListCredentialsByDID(ctx, doc.ID)
	if err != nil {
		return nil, fmt.Errorf("list credentials: %w", err)
	}

	credResults := make([]CredentialResult, len(creds))
	for i, cred := range creds {
		var transports []string
		if err := json.Unmarshal(cred.Transports, &transports); err != nil {
			transports = []string{}
		}

		authenticator := ""
		if cred.Authenticator != nil {
			authenticator = *cred.Authenticator
		}

		credResults[i] = CredentialResult{
			ID:             cred.ID,
			CredentialID:   cred.CredentialID,
			DeviceName:     cred.DeviceName,
			DeviceType:     cred.DeviceType,
			Authenticator:  authenticator,
			Transports:     transports,
			SignCount:      cred.SignCount,
			IsDiscoverable: cred.IsDiscoverable == 1,
			BackedUp:       cred.BackedUp == 1,
			CreatedAt:      cred.CreatedAt,
			LastUsed:       cred.LastUsed,
		}
	}

	return &DIDDocumentResult{
		DID:                 doc.Did,
		Controller:          doc.Controller,
		VerificationMethods: vmResults,
		Accounts:            accountResults,
		Credentials:         credResults,
	}, nil
}

// ResolveCurrentDID resolves the current DID document.
func (am *ActionManager) ResolveCurrentDID(ctx context.Context) (*DIDDocumentResult, error) {
	am.kb.mu.RLock()
	did := am.kb.did
	am.kb.mu.RUnlock()

	if did == "" {
		return nil, fmt.Errorf("no DID initialized")
	}

	return am.ResolveDID(ctx, did)
}
