package rest

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"
)

type addressListRow struct {
	Address        string `json:"address"`
	TxSent         int64  `json:"tx_sent"`
	TxReceived     int64  `json:"tx_received"`
	TransactionCnt int64  `json:"transaction_count"`
	TokenCount     int64  `json:"token_count"`
	IsContract     bool   `json:"is_contract"`
	Label          string `json:"label,omitempty"`
	LastSeenAt     string `json:"last_seen_at,omitempty"`
	FirstSeenAt    string `json:"first_seen_at,omitempty"`
}

// handleListAddresses handles GET /api/v1/addresses
func (s *Server) handleListAddresses(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeMethodNotAllowed(w)
		return
	}
	if !s.requireDB(w) {
		return
	}

	page, pageSize, err := validatePagination(
		r.URL.Query().Get("page"),
		r.URL.Query().Get("page_size"),
	)
	if err != nil {
		writeValidationError(w, err)
		return
	}

	offset := (page - 1) * pageSize
	search := strings.TrimSpace(r.URL.Query().Get("q"))

	whereClause := ""
	args := []interface{}{s.chainID}
	if search != "" {
		whereClause = "WHERE LOWER(a.address) LIKE LOWER($2) OR LOWER(COALESCE(l.label, '')) LIKE LOWER($2)"
		args = append(args, "%"+search+"%")
	}

	query := fmt.Sprintf(`
		WITH activity AS (
			SELECT
				address,
				COUNT(*) FILTER (WHERE direction = 'sent') AS tx_sent,
				COUNT(*) FILTER (WHERE direction = 'received') AS tx_received,
				COUNT(*) AS transaction_count,
				MIN(seen_at) AS first_seen_at,
				MAX(seen_at) AS last_seen_at
			FROM (
				SELECT
					LOWER(t.from_address) AS address,
					'sent' AS direction,
					b.timestamp AS seen_at
				FROM transactions t
				JOIN blocks b ON b.chain_id = t.chain_id AND b.number = t.block_number
				WHERE t.chain_id = $1 AND t.from_address IS NOT NULL AND t.from_address <> ''
				UNION ALL
				SELECT
					LOWER(t.to_address) AS address,
					'received' AS direction,
					b.timestamp AS seen_at
				FROM transactions t
				JOIN blocks b ON b.chain_id = t.chain_id AND b.number = t.block_number
				WHERE t.chain_id = $1 AND t.to_address IS NOT NULL AND t.to_address <> ''
			) entries
			GROUP BY address
		),
		token_activity AS (
			SELECT address, COUNT(DISTINCT token_contract) AS token_count
			FROM (
				SELECT LOWER(from_address) AS address, token_contract
				FROM token_transfers
				WHERE chain_id = $1 AND from_address IS NOT NULL AND from_address <> ''
				UNION ALL
				SELECT LOWER(to_address) AS address, token_contract
				FROM token_transfers
				WHERE chain_id = $1 AND to_address IS NOT NULL AND to_address <> ''
			) tokens
			GROUP BY address
		),
		label_activity AS (
			SELECT DISTINCT ON (LOWER(address))
				LOWER(address) AS address,
				label
			FROM address_labels
			WHERE chain_id = $1 AND label_type = 'public'
			ORDER BY LOWER(address), updated_at DESC, id DESC
		),
		contract_activity AS (
			SELECT LOWER(address) AS address, TRUE AS is_contract
			FROM contracts
			WHERE chain_id = $1
		)
		SELECT
			a.address,
			a.tx_sent,
			a.tx_received,
			a.transaction_count,
			COALESCE(t.token_count, 0) AS token_count,
			COALESCE(c.is_contract, FALSE) AS is_contract,
			COALESCE(l.label, '') AS label,
			COALESCE(a.last_seen_at::text, '') AS last_seen_at,
			COALESCE(a.first_seen_at::text, '') AS first_seen_at
		FROM activity a
		LEFT JOIN token_activity t ON t.address = a.address
		LEFT JOIN label_activity l ON l.address = a.address
		LEFT JOIN contract_activity c ON c.address = a.address
		%s
		ORDER BY a.transaction_count DESC, a.last_seen_at DESC NULLS LAST, a.address ASC
		LIMIT $%d OFFSET $%d
	`, whereClause, len(args)+1, len(args)+2)

	args = append(args, pageSize, offset)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	rows, err := s.db.Query(ctx, query, args...)
	if err != nil {
		writeInternalError(w, "Database error")
		return
	}
	defer rows.Close()

	items := []addressListRow{}
	for rows.Next() {
		var row addressListRow
		if err := rows.Scan(
			&row.Address,
			&row.TxSent,
			&row.TxReceived,
			&row.TransactionCnt,
			&row.TokenCount,
			&row.IsContract,
			&row.Label,
			&row.LastSeenAt,
			&row.FirstSeenAt,
		); err != nil {
			continue
		}
		items = append(items, row)
	}

	response := map[string]interface{}{
		"data": items,
		"meta": map[string]interface{}{
			"pagination": map[string]interface{}{
				"page":      page,
				"page_size": pageSize,
			},
		},
	}

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