package main

import (
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"github.com/hyperledger/fabric-contract-api-go/contractapi"
)

// TokenizedAssetContract provides functions for managing tokenized assets
type TokenizedAssetContract struct {
	contractapi.Contract
}

// TokenizedAsset represents a tokenized asset on Fabric
type TokenizedAsset struct {
	TokenID         string                 `json:"tokenId"`
	UnderlyingAsset string                 `json:"underlyingAsset"`
	Amount          string                 `json:"amount"`
	Issuer          string                 `json:"issuer"`
	BackingReserve  string                 `json:"backingReserve"`
	Status          string                 `json:"status"` // minted, transferred, redeemed
	RegulatoryFlags map[string]interface{} `json:"regulatoryFlags"`
	CreatedAt       string                 `json:"createdAt"`
	UpdatedAt       string                 `json:"updatedAt"`
}

// MintRequest represents a request to mint tokenized assets
type MintRequest struct {
	TokenID         string                 `json:"tokenId"`
	UnderlyingAsset string                 `json:"underlyingAsset"`
	Amount          string                 `json:"amount"`
	Issuer          string                 `json:"issuer"`
	ReserveProof    string                 `json:"reserveProof"`
	RegulatoryFlags map[string]interface{} `json:"regulatoryFlags"`
}

// TransferRequest represents a request to transfer tokenized assets
type TransferRequest struct {
	TokenID    string `json:"tokenId"`
	From       string `json:"from"`
	To         string `json:"to"`
	Amount     string `json:"amount"`
	Regulatory map[string]interface{} `json:"regulatory"`
}

// RedemptionRequest represents a request to redeem tokenized assets
type RedemptionRequest struct {
	TokenID      string `json:"tokenId"`
	Redeemer     string `json:"redeemer"`
	Amount       string `json:"amount"`
	RedemptionProof string `json:"redemptionProof"`
}

// InitLedger initializes the ledger with sample data (for testing)
func (s *TokenizedAssetContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
	assets := []TokenizedAsset{
		{
			TokenID:         "EUR-T-2025-001",
			UnderlyingAsset: "EUR",
			Amount:          "1000000.00",
			Issuer:          "DBIS",
			BackingReserve:  "1:1",
			Status:          "minted",
			RegulatoryFlags: map[string]interface{}{
				"kyc": true,
				"aml": true,
				"regulatoryApproval": true,
			},
			CreatedAt: time.Now().Format(time.RFC3339),
			UpdatedAt: time.Now().Format(time.RFC3339),
		},
	}

	for _, asset := range assets {
		assetJSON, err := json.Marshal(asset)
		if err != nil {
			return err
		}

		err = ctx.GetStub().PutState(asset.TokenID, assetJSON)
		if err != nil {
			return fmt.Errorf("failed to put asset to world state: %v", err)
		}
	}

	return nil
}

// MintToken mints a new tokenized asset after reserve verification
func (s *TokenizedAssetContract) MintToken(ctx contractapi.TransactionContextInterface, requestJSON string) error {
	var request MintRequest
	err := json.Unmarshal([]byte(requestJSON), &request)
	if err != nil {
		return fmt.Errorf("failed to unmarshal mint request: %v", err)
	}

	// Check if token already exists
	existing, err := ctx.GetStub().GetState(request.TokenID)
	if err != nil {
		return fmt.Errorf("failed to read from world state: %v", err)
	}
	if existing != nil {
		return fmt.Errorf("token %s already exists", request.TokenID)
	}

	// Verify reserve proof (in production, this would call reserve manager chaincode)
	// For now, we assume reserve proof is valid if provided
	if request.ReserveProof == "" {
		return fmt.Errorf("reserve proof is required")
	}

	// Check SolaceNet capability (would integrate with SolaceNet service)
	// This is a placeholder - in production, call SolaceNet API
	clientID := ctx.GetClientIdentity()
	canMint, err := s.checkSolaceNetCapability(ctx, clientID.GetID(), "tokenization.mint")
	if err != nil {
		return fmt.Errorf("failed to check SolaceNet capability: %v", err)
	}
	if !canMint {
		return fmt.Errorf("client %s does not have tokenization.mint capability", clientID.GetID())
	}

	// Create tokenized asset
	asset := TokenizedAsset{
		TokenID:         request.TokenID,
		UnderlyingAsset: request.UnderlyingAsset,
		Amount:          request.Amount,
		Issuer:          request.Issuer,
		BackingReserve:  "1:1",
		Status:          "minted",
		RegulatoryFlags: request.RegulatoryFlags,
		CreatedAt:       time.Now().Format(time.RFC3339),
		UpdatedAt:       time.Now().Format(time.RFC3339),
	}

	assetJSON, err := json.Marshal(asset)
	if err != nil {
		return err
	}

	err = ctx.GetStub().PutState(request.TokenID, assetJSON)
	if err != nil {
		return fmt.Errorf("failed to put asset to world state: %v", err)
	}

	// Emit event
	eventPayload := fmt.Sprintf(`{"tokenId":"%s","action":"mint","amount":"%s","issuer":"%s"}`, 
		request.TokenID, request.Amount, request.Issuer)
	err = ctx.GetStub().SetEvent("TokenMinted", []byte(eventPayload))
	if err != nil {
		return fmt.Errorf("failed to emit event: %v", err)
	}

	return nil
}

// TransferToken transfers tokenized assets with regulatory checks
func (s *TokenizedAssetContract) TransferToken(ctx contractapi.TransactionContextInterface, requestJSON string) error {
	var request TransferRequest
	err := json.Unmarshal([]byte(requestJSON), &request)
	if err != nil {
		return fmt.Errorf("failed to unmarshal transfer request: %v", err)
	}

	// Get token
	assetJSON, err := ctx.GetStub().GetState(request.TokenID)
	if err != nil {
		return fmt.Errorf("failed to read token from world state: %v", err)
	}
	if assetJSON == nil {
		return fmt.Errorf("token %s does not exist", request.TokenID)
	}

	var asset TokenizedAsset
	err = json.Unmarshal(assetJSON, &asset)
	if err != nil {
		return err
	}

	// Verify sender has permission
	clientID := ctx.GetClientIdentity()
	if asset.Issuer != clientID.GetID() && request.From != clientID.GetID() {
		return fmt.Errorf("client %s is not authorized to transfer this token", clientID.GetID())
	}

	// Check SolaceNet capability
	canTransfer, err := s.checkSolaceNetCapability(ctx, clientID.GetID(), "tokenization.transfer")
	if err != nil {
		return fmt.Errorf("failed to check SolaceNet capability: %v", err)
	}
	if !canTransfer {
		return fmt.Errorf("client %s does not have tokenization.transfer capability", clientID.GetID())
	}

	// Verify amounts (simplified - in production, use proper decimal handling)
	requestAmount, err := strconv.ParseFloat(request.Amount, 64)
	if err != nil {
		return fmt.Errorf("invalid amount: %v", err)
	}

	currentAmount, err := strconv.ParseFloat(asset.Amount, 64)
	if err != nil {
		return fmt.Errorf("invalid current amount: %v", err)
	}

	if requestAmount > currentAmount {
		return fmt.Errorf("insufficient balance: requested %f, available %f", requestAmount, currentAmount)
	}

	// Update token
	newAmount := currentAmount - requestAmount
	asset.Amount = fmt.Sprintf("%.2f", newAmount)
	asset.Status = "transferred"
	asset.UpdatedAt = time.Now().Format(time.RFC3339)

	// Merge regulatory flags
	for k, v := range request.Regulatory {
		asset.RegulatoryFlags[k] = v
	}

	updatedJSON, err := json.Marshal(asset)
	if err != nil {
		return err
	}

	err = ctx.GetStub().PutState(request.TokenID, updatedJSON)
	if err != nil {
		return fmt.Errorf("failed to update asset in world state: %v", err)
	}

	// Emit event
	eventPayload := fmt.Sprintf(`{"tokenId":"%s","action":"transfer","from":"%s","to":"%s","amount":"%s"}`,
		request.TokenID, request.From, request.To, request.Amount)
	err = ctx.GetStub().SetEvent("TokenTransferred", []byte(eventPayload))
	if err != nil {
		return fmt.Errorf("failed to emit event: %v", err)
	}

	return nil
}

// RedeemToken redeems tokenized assets back to underlying asset
func (s *TokenizedAssetContract) RedeemToken(ctx contractapi.TransactionContextInterface, requestJSON string) error {
	var request RedemptionRequest
	err := json.Unmarshal([]byte(requestJSON), &request)
	if err != nil {
		return fmt.Errorf("failed to unmarshal redemption request: %v", err)
	}

	// Get token
	assetJSON, err := ctx.GetStub().GetState(request.TokenID)
	if err != nil {
		return fmt.Errorf("failed to read token from world state: %v", err)
	}
	if assetJSON == nil {
		return fmt.Errorf("token %s does not exist", request.TokenID)
	}

	var asset TokenizedAsset
	err = json.Unmarshal(assetJSON, &asset)
	if err != nil {
		return err
	}

	// Verify redemption proof
	if request.RedemptionProof == "" {
		return fmt.Errorf("redemption proof is required")
	}

	// Check SolaceNet capability
	clientID := ctx.GetClientIdentity()
	canRedeem, err := s.checkSolaceNetCapability(ctx, clientID.GetID(), "tokenization.redeem")
	if err != nil {
		return fmt.Errorf("failed to check SolaceNet capability: %v", err)
	}
	if !canRedeem {
		return fmt.Errorf("client %s does not have tokenization.redeem capability", clientID.GetID())
	}

	// Verify amounts
	requestAmount, err := strconv.ParseFloat(request.Amount, 64)
	if err != nil {
		return fmt.Errorf("invalid amount: %v", err)
	}

	currentAmount, err := strconv.ParseFloat(asset.Amount, 64)
	if err != nil {
		return fmt.Errorf("invalid current amount: %v", err)
	}

	if requestAmount > currentAmount {
		return fmt.Errorf("insufficient balance: requested %f, available %f", requestAmount, currentAmount)
	}

	// Update token
	newAmount := currentAmount - requestAmount
	asset.Amount = fmt.Sprintf("%.2f", newAmount)
	asset.Status = "redeemed"
	asset.UpdatedAt = time.Now().Format(time.RFC3339)

	updatedJSON, err := json.Marshal(asset)
	if err != nil {
		return err
	}

	err = ctx.GetStub().PutState(request.TokenID, updatedJSON)
	if err != nil {
		return fmt.Errorf("failed to update asset in world state: %v", err)
	}

	// Emit event
	eventPayload := fmt.Sprintf(`{"tokenId":"%s","action":"redeem","redeemer":"%s","amount":"%s"}`,
		request.TokenID, request.Redeemer, request.Amount)
	err = ctx.GetStub().SetEvent("TokenRedeemed", []byte(eventPayload))
	if err != nil {
		return fmt.Errorf("failed to emit event: %v", err)
	}

	return nil
}

// GetToken returns the tokenized asset details
func (s *TokenizedAssetContract) GetToken(ctx contractapi.TransactionContextInterface, tokenID string) (*TokenizedAsset, error) {
	assetJSON, err := ctx.GetStub().GetState(tokenID)
	if err != nil {
		return nil, fmt.Errorf("failed to read from world state: %v", err)
	}
	if assetJSON == nil {
		return nil, fmt.Errorf("token %s does not exist", tokenID)
	}

	var asset TokenizedAsset
	err = json.Unmarshal(assetJSON, &asset)
	if err != nil {
		return nil, err
	}

	return &asset, nil
}

// GetAllTokens returns all tokenized assets (with pagination support)
func (s *TokenizedAssetContract) GetAllTokens(ctx contractapi.TransactionContextInterface) ([]*TokenizedAsset, error) {
	resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	var assets []*TokenizedAsset
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return nil, err
		}

		var asset TokenizedAsset
		err = json.Unmarshal(queryResponse.Value, &asset)
		if err != nil {
			return nil, err
		}
		assets = append(assets, &asset)
	}

	return assets, nil
}

// checkSolaceNetCapability checks if a client has a SolaceNet capability
// In production, this would call SolaceNet API or use chaincode-to-chaincode invocation
func (s *TokenizedAssetContract) checkSolaceNetCapability(ctx contractapi.TransactionContextInterface, clientID, capability string) (bool, error) {
	// Placeholder implementation
	// In production, this would:
	// 1. Call SolaceNet API via external service
	// 2. Or use chaincode-to-chaincode invocation if SolaceNet is on same network
	// 3. Or use Cacti to bridge to SolaceNet service
	
	// For now, return true for testing
	// In production, implement actual SolaceNet integration
	return true, nil
}

func main() {
	chaincode, err := contractapi.NewChaincode(&TokenizedAssetContract{})
	if err != nil {
		fmt.Printf("Error creating tokenized asset chaincode: %v", err)
		return
	}

	if err := chaincode.Start(); err != nil {
		fmt.Printf("Error starting tokenized asset chaincode: %v", err)
	}
}
