package rest

import (
	"context"
	"fmt"
	"regexp"
	"strings"
	"time"
)

var (
	addressPattern     = regexp.MustCompile(`0x[a-fA-F0-9]{40}`)
	transactionPattern = regexp.MustCompile(`0x[a-fA-F0-9]{64}`)
	blockRefPattern    = regexp.MustCompile(`(?i)\bblock\s+#?(\d+)\b`)
)

func (s *Server) buildAIContext(ctx context.Context, query string, pageContext map[string]string) (AIContextEnvelope, []string) {
	warnings := []string{}
	envelope := AIContextEnvelope{
		ChainID:          s.chainID,
		Explorer:         "SolaceScan",
		PageContext:      compactStringMap(pageContext),
		CapabilityNotice: "This assistant is wired for read-only explorer analysis. It can summarize indexed chain data, liquidity routes, and curated workspace docs, but it does not sign transactions or execute private operations.",
	}

	sources := []AIContextSource{
		{Type: "system", Label: "Explorer REST backend"},
	}

	if stats, err := s.queryAIStats(ctx); err == nil {
		envelope.Stats = stats
		sources = append(sources, AIContextSource{Type: "database", Label: "Explorer indexer database"})
	} else if err != nil {
		warnings = append(warnings, "indexed explorer stats unavailable: "+err.Error())
	}

	if strings.TrimSpace(query) != "" {
		if txHash := firstRegexMatch(transactionPattern, query); txHash != "" && s.db != nil {
			if tx, err := s.queryAITransaction(ctx, txHash); err == nil && len(tx) > 0 {
				envelope.Transaction = tx
			} else if err != nil {
				warnings = append(warnings, "transaction context unavailable: "+err.Error())
			}
		}

		if addr := firstRegexMatch(addressPattern, query); addr != "" && s.db != nil {
			if addressInfo, err := s.queryAIAddress(ctx, addr); err == nil && len(addressInfo) > 0 {
				envelope.Address = addressInfo
			} else if err != nil {
				warnings = append(warnings, "address context unavailable: "+err.Error())
			}
		}

		if blockNumber := extractBlockReference(query); blockNumber > 0 && s.db != nil {
			if block, err := s.queryAIBlock(ctx, blockNumber); err == nil && len(block) > 0 {
				envelope.Block = block
			} else if err != nil {
				warnings = append(warnings, "block context unavailable: "+err.Error())
			}
		}
	}

	if routeMatches, routeWarning := s.queryAIRoutes(ctx, query); len(routeMatches) > 0 {
		envelope.RouteMatches = routeMatches
		sources = append(sources, AIContextSource{Type: "routes", Label: "Token aggregation live routes", Origin: firstNonEmptyEnv("TOKEN_AGGREGATION_API_BASE", "TOKEN_AGGREGATION_URL", "TOKEN_AGGREGATION_BASE_URL")})
	} else if routeWarning != "" {
		warnings = append(warnings, routeWarning)
	}

	if docs, root, docWarning := loadAIDocSnippets(query); len(docs) > 0 {
		envelope.DocSnippets = docs
		sources = append(sources, AIContextSource{Type: "docs", Label: "Workspace docs", Origin: root})
	} else if docWarning != "" {
		warnings = append(warnings, docWarning)
	}

	envelope.Sources = sources
	return envelope, uniqueStrings(warnings)
}

func (s *Server) queryAIStats(ctx context.Context) (map[string]any, error) {
	if s.db == nil {
		return nil, fmt.Errorf("database unavailable")
	}
	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
	defer cancel()

	stats := map[string]any{}

	var totalBlocks int64
	if err := s.db.QueryRow(ctx, `SELECT COUNT(*) FROM blocks WHERE chain_id = $1`, s.chainID).Scan(&totalBlocks); err == nil {
		stats["total_blocks"] = totalBlocks
	}

	var totalTransactions int64
	if err := s.db.QueryRow(ctx, `SELECT COUNT(*) FROM transactions WHERE chain_id = $1`, s.chainID).Scan(&totalTransactions); err == nil {
		stats["total_transactions"] = totalTransactions
	}

	var totalAddresses int64
	if err := s.db.QueryRow(ctx, `SELECT COUNT(*) FROM (
		SELECT from_address AS address
		FROM transactions
		WHERE chain_id = $1 AND from_address IS NOT NULL AND from_address <> ''
		UNION
		SELECT to_address AS address
		FROM transactions
		WHERE chain_id = $1 AND to_address IS NOT NULL AND to_address <> ''
	) unique_addresses`, s.chainID).Scan(&totalAddresses); err == nil {
		stats["total_addresses"] = totalAddresses
	}

	var latestBlock int64
	if err := s.db.QueryRow(ctx, `SELECT COALESCE(MAX(number), 0) FROM blocks WHERE chain_id = $1`, s.chainID).Scan(&latestBlock); err == nil {
		stats["latest_block"] = latestBlock
	}

	if len(stats) == 0 {
		var totalBlocks int64
		if err := s.db.QueryRow(ctx, `SELECT COUNT(*) FROM blocks`).Scan(&totalBlocks); err == nil {
			stats["total_blocks"] = totalBlocks
		}

		var totalTransactions int64
		if err := s.db.QueryRow(ctx, `SELECT COUNT(*) FROM transactions`).Scan(&totalTransactions); err == nil {
			stats["total_transactions"] = totalTransactions
		}

		var totalAddresses int64
		if err := s.db.QueryRow(ctx, `SELECT COUNT(*) FROM addresses`).Scan(&totalAddresses); err == nil {
			stats["total_addresses"] = totalAddresses
		}

		var latestBlock int64
		if err := s.db.QueryRow(ctx, `SELECT COALESCE(MAX(number), 0) FROM blocks`).Scan(&latestBlock); err == nil {
			stats["latest_block"] = latestBlock
		}
	}

	if len(stats) == 0 {
		return nil, fmt.Errorf("no indexed stats available")
	}
	return stats, nil
}

func (s *Server) queryAITransaction(ctx context.Context, hash string) (map[string]any, error) {
	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
	defer cancel()

	query := `
		SELECT hash, block_number, from_address, to_address, value, gas_used, gas_price, status, timestamp_iso
		FROM transactions
		WHERE chain_id = $1 AND hash = $2
		LIMIT 1
	`

	var txHash, fromAddress, value string
	var blockNumber int64
	var toAddress *string
	var gasUsed, gasPrice *int64
	var status *int64
	var timestampISO *string

	err := s.db.QueryRow(ctx, query, s.chainID, hash).Scan(
		&txHash, &blockNumber, &fromAddress, &toAddress, &value, &gasUsed, &gasPrice, &status, &timestampISO,
	)
	if err != nil {
		normalizedHash := normalizeHexString(hash)
		blockscoutQuery := `
			SELECT
				concat('0x', encode(hash, 'hex')) AS hash,
				block_number,
				concat('0x', encode(from_address_hash, 'hex')) AS from_address,
				CASE
					WHEN to_address_hash IS NULL THEN NULL
					ELSE concat('0x', encode(to_address_hash, 'hex'))
				END AS to_address,
				COALESCE(value::text, '0') AS value,
				gas_used,
				gas_price,
				status,
				TO_CHAR(block_timestamp AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') AS timestamp_iso
			FROM transactions
			WHERE hash = decode($1, 'hex')
			LIMIT 1
		`
		if fallbackErr := s.db.QueryRow(ctx, blockscoutQuery, normalizedHash).Scan(
			&txHash, &blockNumber, &fromAddress, &toAddress, &value, &gasUsed, &gasPrice, &status, &timestampISO,
		); fallbackErr != nil {
			return nil, err
		}
	}

	tx := map[string]any{
		"hash":         txHash,
		"block_number": blockNumber,
		"from_address": fromAddress,
		"value":        value,
	}
	if toAddress != nil {
		tx["to_address"] = *toAddress
	}
	if gasUsed != nil {
		tx["gas_used"] = *gasUsed
	}
	if gasPrice != nil {
		tx["gas_price"] = *gasPrice
	}
	if status != nil {
		tx["status"] = *status
	}
	if timestampISO != nil {
		tx["timestamp_iso"] = *timestampISO
	}
	return tx, nil
}

func (s *Server) queryAIAddress(ctx context.Context, address string) (map[string]any, error) {
	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
	defer cancel()

	address = normalizeAddress(address)

	result := map[string]any{
		"address": address,
	}

	var txCount int64
	if err := s.db.QueryRow(ctx, `SELECT COUNT(*) FROM transactions WHERE chain_id = $1 AND (LOWER(from_address) = $2 OR LOWER(to_address) = $2)`, s.chainID, address).Scan(&txCount); err == nil {
		result["transaction_count"] = txCount
	}

	var tokenCount int64
	if err := s.db.QueryRow(ctx, `SELECT COUNT(DISTINCT token_contract) FROM token_transfers WHERE chain_id = $1 AND (LOWER(from_address) = $2 OR LOWER(to_address) = $2)`, s.chainID, address).Scan(&tokenCount); err == nil {
		result["token_count"] = tokenCount
	}

	var recentHashes []string
	rows, err := s.db.Query(ctx, `
		SELECT hash
		FROM transactions
		WHERE chain_id = $1 AND (LOWER(from_address) = $2 OR LOWER(to_address) = $2)
		ORDER BY block_number DESC, transaction_index DESC
		LIMIT 5
	`, s.chainID, address)
	if err == nil {
		defer rows.Close()
		for rows.Next() {
			var hash string
			if scanErr := rows.Scan(&hash); scanErr == nil {
				recentHashes = append(recentHashes, hash)
			}
		}
	}
	if len(recentHashes) > 0 {
		result["recent_transactions"] = recentHashes
	}

	if len(result) == 1 {
		normalizedAddress := normalizeHexString(address)

		var blockscoutTxCount int64
		var blockscoutTokenCount int64
		blockscoutAddressQuery := `
			SELECT
				COALESCE(transactions_count, 0),
				COALESCE(token_transfers_count, 0)
			FROM addresses
			WHERE hash = decode($1, 'hex')
			LIMIT 1
		`
		if err := s.db.QueryRow(ctx, blockscoutAddressQuery, normalizedAddress).Scan(&blockscoutTxCount, &blockscoutTokenCount); err == nil {
			result["transaction_count"] = blockscoutTxCount
			result["token_count"] = blockscoutTokenCount
		}

		var liveTxCount int64
		if err := s.db.QueryRow(ctx, `
			SELECT COUNT(*)
			FROM transactions
			WHERE from_address_hash = decode($1, 'hex') OR to_address_hash = decode($1, 'hex')
		`, normalizedAddress).Scan(&liveTxCount); err == nil && liveTxCount > 0 {
			result["transaction_count"] = liveTxCount
		}

		var liveTokenCount int64
		if err := s.db.QueryRow(ctx, `
			SELECT COUNT(DISTINCT token_contract_address_hash)
			FROM token_transfers
			WHERE from_address_hash = decode($1, 'hex') OR to_address_hash = decode($1, 'hex')
		`, normalizedAddress).Scan(&liveTokenCount); err == nil && liveTokenCount > 0 {
			result["token_count"] = liveTokenCount
		}

		rows, err := s.db.Query(ctx, `
			SELECT concat('0x', encode(hash, 'hex'))
			FROM transactions
			WHERE from_address_hash = decode($1, 'hex') OR to_address_hash = decode($1, 'hex')
			ORDER BY block_number DESC, index DESC
			LIMIT 5
		`, normalizedAddress)
		if err == nil {
			defer rows.Close()
			for rows.Next() {
				var hash string
				if scanErr := rows.Scan(&hash); scanErr == nil {
					recentHashes = append(recentHashes, hash)
				}
			}
		}
		if len(recentHashes) > 0 {
			result["recent_transactions"] = recentHashes
		}
	}

	if len(result) == 1 {
		return nil, fmt.Errorf("address not found")
	}
	return result, nil
}

func (s *Server) queryAIBlock(ctx context.Context, blockNumber int64) (map[string]any, error) {
	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
	defer cancel()

	query := `
		SELECT number, hash, parent_hash, transaction_count, gas_used, gas_limit, timestamp_iso
		FROM blocks
		WHERE chain_id = $1 AND number = $2
		LIMIT 1
	`

	var number int64
	var hash, parentHash string
	var transactionCount int64
	var gasUsed, gasLimit int64
	var timestampISO *string

	err := s.db.QueryRow(ctx, query, s.chainID, blockNumber).Scan(&number, &hash, &parentHash, &transactionCount, &gasUsed, &gasLimit, &timestampISO)
	if err != nil {
		blockscoutQuery := `
			SELECT
				number,
				concat('0x', encode(hash, 'hex')) AS hash,
				concat('0x', encode(parent_hash, 'hex')) AS parent_hash,
				(SELECT COUNT(*) FROM transactions WHERE block_number = b.number) AS transaction_count,
				gas_used,
				gas_limit,
				TO_CHAR(timestamp AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') AS timestamp_iso
			FROM blocks b
			WHERE number = $1
			LIMIT 1
		`
		if fallbackErr := s.db.QueryRow(ctx, blockscoutQuery, blockNumber).Scan(&number, &hash, &parentHash, &transactionCount, &gasUsed, &gasLimit, &timestampISO); fallbackErr != nil {
			return nil, err
		}
	}

	block := map[string]any{
		"number":            number,
		"hash":              hash,
		"parent_hash":       parentHash,
		"transaction_count": transactionCount,
		"gas_used":          gasUsed,
		"gas_limit":         gasLimit,
	}
	if timestampISO != nil {
		block["timestamp_iso"] = *timestampISO
	}
	return block, nil
}

func extractBlockReference(query string) int64 {
	match := blockRefPattern.FindStringSubmatch(query)
	if len(match) != 2 {
		return 0
	}
	var value int64
	fmt.Sscan(match[1], &value)
	return value
}
