package track2

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"github.com/ethereum/go-ethereum/common"
	"github.com/jackc/pgx/v5/pgxpool"
)

var track2HashPattern = regexp.MustCompile(`^0x[0-9a-fA-F]{64}$`)

// Server handles Track 2 endpoints
type Server struct {
	db      *pgxpool.Pool
	chainID int
}

// NewServer creates a new Track 2 server
func NewServer(db *pgxpool.Pool, chainID int) *Server {
	return &Server{
		db:      db,
		chainID: chainID,
	}
}

// HandleAddressTransactions handles GET /api/v1/track2/address/:addr/txs
func (s *Server) HandleAddressTransactions(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
		return
	}
	if !s.requireDB(w) {
		return
	}

	path := strings.TrimPrefix(r.URL.Path, "/api/v1/track2/address/")
	parts := strings.Split(path, "/")
	if len(parts) < 2 || parts[1] != "txs" {
		writeError(w, http.StatusBadRequest, "bad_request", "Invalid path")
		return
	}

	address, err := normalizeTrack2Address(parts[0])
	if err != nil {
		writeError(w, http.StatusBadRequest, "bad_request", err.Error())
		return
	}
	page, _ := strconv.Atoi(r.URL.Query().Get("page"))
	if page < 1 {
		page = 1
	}
	limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
	if limit < 1 || limit > 100 {
		limit = 20
	}
	offset := (page - 1) * limit

	query := `
		SELECT hash, from_address, to_address, value, block_number, timestamp, status
		FROM transactions
		WHERE chain_id = $1 AND (LOWER(from_address) = $2 OR LOWER(to_address) = $2)
		ORDER BY block_number DESC, timestamp DESC
		LIMIT $3 OFFSET $4
	`

	rows, err := s.db.Query(r.Context(), query, s.chainID, address, limit, offset)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "database_error", err.Error())
		return
	}
	defer rows.Close()

	transactions := []map[string]interface{}{}
	for rows.Next() {
		var hash, from, to, value, status string
		var blockNumber int64
		var timestamp interface{}

		if err := rows.Scan(&hash, &from, &to, &value, &blockNumber, &timestamp, &status); err != nil {
			continue
		}

		direction := "received"
		if strings.ToLower(from) == address {
			direction = "sent"
		}

		transactions = append(transactions, map[string]interface{}{
			"hash":         hash,
			"from":         from,
			"to":           to,
			"value":        value,
			"block_number": blockNumber,
			"timestamp":    timestamp,
			"status":       status,
			"direction":    direction,
		})
	}

	// Get total count
	var total int
	countQuery := `SELECT COUNT(*) FROM transactions WHERE chain_id = $1 AND (LOWER(from_address) = $2 OR LOWER(to_address) = $2)`
	s.db.QueryRow(r.Context(), countQuery, s.chainID, address).Scan(&total)

	response := map[string]interface{}{
		"data": transactions,
		"pagination": map[string]interface{}{
			"page":        page,
			"limit":       limit,
			"total":       total,
			"total_pages": (total + limit - 1) / limit,
		},
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// HandleAddressTokens handles GET /api/v1/track2/address/:addr/tokens
func (s *Server) HandleAddressTokens(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
		return
	}
	if !s.requireDB(w) {
		return
	}

	path := strings.TrimPrefix(r.URL.Path, "/api/v1/track2/address/")
	parts := strings.Split(path, "/")
	if len(parts) < 2 || parts[1] != "tokens" {
		writeError(w, http.StatusBadRequest, "bad_request", "Invalid path")
		return
	}

	address, err := normalizeTrack2Address(parts[0])
	if err != nil {
		writeError(w, http.StatusBadRequest, "bad_request", err.Error())
		return
	}

	query := `
		SELECT token_contract, balance, last_updated_timestamp
		FROM token_balances
		WHERE LOWER(address) = $1 AND chain_id = $2 AND balance > 0
		ORDER BY balance DESC
	`

	rows, err := s.db.Query(r.Context(), query, address, s.chainID)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "database_error", err.Error())
		return
	}
	defer rows.Close()

	tokens := []map[string]interface{}{}
	for rows.Next() {
		var contract, balance string
		var lastUpdated interface{}

		if err := rows.Scan(&contract, &balance, &lastUpdated); err != nil {
			continue
		}

		tokens = append(tokens, map[string]interface{}{
			"contract":          contract,
			"balance":           balance,
			"balance_formatted": nil,
			"last_updated":      lastUpdated,
		})
	}

	response := map[string]interface{}{
		"data": map[string]interface{}{
			"address":      address,
			"tokens":       tokens,
			"total_tokens": len(tokens),
		},
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// HandleTokenInfo handles GET /api/v1/track2/token/:contract
func (s *Server) HandleTokenInfo(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
		return
	}
	if !s.requireDB(w) {
		return
	}

	path := strings.TrimPrefix(r.URL.Path, "/api/v1/track2/token/")
	contract, err := normalizeTrack2Address(path)
	if err != nil {
		writeError(w, http.StatusBadRequest, "bad_request", err.Error())
		return
	}

	// Get token info from token_transfers
	query := `
		SELECT 
			(
				SELECT COUNT(*)
				FROM (
					SELECT from_address AS address
					FROM token_transfers
					WHERE token_contract = $1
						AND chain_id = $2
						AND timestamp >= NOW() - INTERVAL '24 hours'
						AND from_address IS NOT NULL
						AND from_address <> ''
					UNION
					SELECT to_address AS address
					FROM token_transfers
					WHERE token_contract = $1
						AND chain_id = $2
						AND timestamp >= NOW() - INTERVAL '24 hours'
						AND to_address IS NOT NULL
						AND to_address <> ''
				) holder_addresses
			) as holders,
			COUNT(*) as transfers_24h,
			SUM(value) as volume_24h
		FROM token_transfers
		WHERE token_contract = $1 AND chain_id = $2
			AND timestamp >= NOW() - INTERVAL '24 hours'
	`

	var holders, transfers24h int
	var volume24h string
	err = s.db.QueryRow(r.Context(), query, contract, s.chainID).Scan(&holders, &transfers24h, &volume24h)
	if err != nil {
		writeError(w, http.StatusNotFound, "not_found", "Token not found")
		return
	}

	response := map[string]interface{}{
		"data": map[string]interface{}{
			"contract":      contract,
			"holders":       holders,
			"transfers_24h": transfers24h,
			"volume_24h":    volume24h,
		},
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// HandleSearch handles GET /api/v1/track2/search?q=
func (s *Server) HandleSearch(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
		return
	}
	if !s.requireDB(w) {
		return
	}

	query := strings.TrimSpace(r.URL.Query().Get("q"))
	if query == "" {
		writeError(w, http.StatusBadRequest, "bad_request", "Query parameter 'q' is required")
		return
	}

	// Try to detect type and search
	var result map[string]interface{}

	// Check if it's a block number
	if blockNum, err := strconv.ParseInt(query, 10, 64); err == nil {
		var hash string
		err := s.db.QueryRow(r.Context(), `SELECT hash FROM blocks WHERE chain_id = $1 AND number = $2`, s.chainID, blockNum).Scan(&hash)
		if err == nil {
			result = map[string]interface{}{
				"type": "block",
				"result": map[string]interface{}{
					"number": blockNum,
					"hash":   hash,
				},
			}
		}
	} else if track2HashPattern.MatchString(query) {
		hash, err := normalizeTrack2Hash(query)
		if err != nil {
			writeError(w, http.StatusBadRequest, "bad_request", err.Error())
			return
		}
		var txHash string
		err = s.db.QueryRow(r.Context(), `SELECT hash FROM transactions WHERE chain_id = $1 AND LOWER(hash) = $2`, s.chainID, hash).Scan(&txHash)
		if err == nil {
			result = map[string]interface{}{
				"type": "transaction",
				"result": map[string]interface{}{
					"hash": txHash,
				},
			}
		}
	} else if common.IsHexAddress(query) {
		address, err := normalizeTrack2Address(query)
		if err != nil {
			writeError(w, http.StatusBadRequest, "bad_request", err.Error())
			return
		}

		var exists bool
		existsQuery := `
			SELECT EXISTS (
				SELECT 1
				FROM addresses
				WHERE chain_id = $1 AND LOWER(address) = $2
				UNION
				SELECT 1
				FROM transactions
				WHERE chain_id = $1 AND (LOWER(from_address) = $2 OR LOWER(to_address) = $2)
				UNION
				SELECT 1
				FROM token_balances
				WHERE chain_id = $1 AND LOWER(address) = $2
			)
		`
		err = s.db.QueryRow(r.Context(), existsQuery, s.chainID, address).Scan(&exists)
		if err == nil && exists {
			var balance string
			err = s.db.QueryRow(r.Context(), `SELECT COALESCE(SUM(balance), '0') FROM token_balances WHERE LOWER(address) = $1 AND chain_id = $2`, address, s.chainID).Scan(&balance)
			if err != nil {
				balance = "0"
			}

			result = map[string]interface{}{
				"type": "address",
				"result": map[string]interface{}{
					"address": address,
					"balance": balance,
				},
			}
		}
	}

	if result == nil {
		writeError(w, http.StatusNotFound, "not_found", "No results found")
		return
	}

	response := map[string]interface{}{
		"data": result,
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// HandleInternalTransactions handles GET /api/v1/track2/address/:addr/internal-txs
func (s *Server) HandleInternalTransactions(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
		return
	}
	if !s.requireDB(w) {
		return
	}

	path := strings.TrimPrefix(r.URL.Path, "/api/v1/track2/address/")
	parts := strings.Split(path, "/")
	if len(parts) < 2 || parts[1] != "internal-txs" {
		writeError(w, http.StatusBadRequest, "bad_request", "Invalid path")
		return
	}

	address, err := normalizeTrack2Address(parts[0])
	if err != nil {
		writeError(w, http.StatusBadRequest, "bad_request", err.Error())
		return
	}
	page, _ := strconv.Atoi(r.URL.Query().Get("page"))
	if page < 1 {
		page = 1
	}
	limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
	if limit < 1 || limit > 100 {
		limit = 20
	}
	offset := (page - 1) * limit

	query := `
		SELECT transaction_hash, from_address, to_address, value, block_number, timestamp
		FROM internal_transactions
		WHERE chain_id = $1 AND (LOWER(from_address) = $2 OR LOWER(to_address) = $2)
		ORDER BY block_number DESC, timestamp DESC
		LIMIT $3 OFFSET $4
	`

	rows, err := s.db.Query(r.Context(), query, s.chainID, address, limit, offset)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "database_error", err.Error())
		return
	}
	defer rows.Close()

	internalTxs := []map[string]interface{}{}
	for rows.Next() {
		var txHash, from, to, value string
		var blockNumber int64
		var timestamp interface{}

		if err := rows.Scan(&txHash, &from, &to, &value, &blockNumber, &timestamp); err != nil {
			continue
		}

		internalTxs = append(internalTxs, map[string]interface{}{
			"transaction_hash": txHash,
			"from":             from,
			"to":               to,
			"value":            value,
			"block_number":     blockNumber,
			"timestamp":        timestamp,
		})
	}

	var total int
	countQuery := `SELECT COUNT(*) FROM internal_transactions WHERE chain_id = $1 AND (LOWER(from_address) = $2 OR LOWER(to_address) = $2)`
	s.db.QueryRow(r.Context(), countQuery, s.chainID, address).Scan(&total)

	response := map[string]interface{}{
		"data": internalTxs,
		"pagination": map[string]interface{}{
			"page":        page,
			"limit":       limit,
			"total":       total,
			"total_pages": (total + limit - 1) / limit,
		},
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

func writeError(w http.ResponseWriter, statusCode int, code, message string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(statusCode)
	json.NewEncoder(w).Encode(map[string]interface{}{
		"error": map[string]interface{}{
			"code":    code,
			"message": message,
		},
	})
}

func (s *Server) requireDB(w http.ResponseWriter) bool {
	if s.db == nil {
		writeError(w, http.StatusServiceUnavailable, "service_unavailable", "database not configured")
		return false
	}
	return true
}

func normalizeTrack2Address(value string) (string, error) {
	trimmed := strings.TrimSpace(value)
	if !common.IsHexAddress(trimmed) {
		return "", fmt.Errorf("invalid address format")
	}
	return strings.ToLower(common.HexToAddress(trimmed).Hex()), nil
}

func normalizeTrack2Hash(value string) (string, error) {
	trimmed := strings.TrimSpace(value)
	if !track2HashPattern.MatchString(trimmed) {
		return "", fmt.Errorf("invalid transaction hash")
	}
	if _, err := hex.DecodeString(trimmed[2:]); err != nil {
		return "", fmt.Errorf("invalid transaction hash")
	}
	return strings.ToLower(trimmed), nil
}
