package track4

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/explorer/backend/api/middleware"
	"github.com/explorer/backend/auth"
	"github.com/jackc/pgx/v5/pgxpool"
)

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

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

// HandleBridgeEvents handles GET /api/v1/track4/operator/bridge/events
func (s *Server) HandleBridgeEvents(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
		return
	}

	operatorAddr, ipAddr, ok := s.requireOperatorAccess(w, r)
	if !ok {
		return
	}

	events, lastUpdate, err := s.loadBridgeEvents(r.Context(), 100)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "database_error", err.Error())
		return
	}
	s.roleMgr.LogOperatorEvent(r.Context(), "bridge_events_read", &s.chainID, operatorAddr, "bridge/events", "read", map[string]interface{}{"event_count": len(events)}, ipAddr, r.UserAgent())

	controlState := map[string]interface{}{
		"paused":                     nil,
		"maintenance_mode":           nil,
		"bridge_control_unavailable": true,
	}
	if !lastUpdate.IsZero() {
		controlState["last_update"] = lastUpdate.UTC().Format(time.RFC3339)
	}

	response := map[string]interface{}{
		"data": map[string]interface{}{
			"events":        events,
			"control_state": controlState,
		},
	}

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

// HandleValidators handles GET /api/v1/track4/operator/validators
func (s *Server) HandleValidators(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
		return
	}

	operatorAddr, ipAddr, ok := s.requireOperatorAccess(w, r)
	if !ok {
		return
	}

	validators, err := s.loadValidatorStatus(r.Context())
	if err != nil {
		writeError(w, http.StatusInternalServerError, "database_error", err.Error())
		return
	}

	s.roleMgr.LogOperatorEvent(r.Context(), "validators_read", &s.chainID, operatorAddr, "validators", "read", map[string]interface{}{"validator_count": len(validators)}, ipAddr, r.UserAgent())

	response := map[string]interface{}{
		"data": map[string]interface{}{
			"validators":        validators,
			"total_validators":  len(validators),
			"active_validators": len(validators),
		},
	}

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

// HandleContracts handles GET /api/v1/track4/operator/contracts
func (s *Server) HandleContracts(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
		return
	}

	operatorAddr, ipAddr, ok := s.requireOperatorAccess(w, r)
	if !ok {
		return
	}

	chainID := s.chainID
	if raw := strings.TrimSpace(r.URL.Query().Get("chain_id")); raw != "" {
		parsed, err := strconv.Atoi(raw)
		if err != nil || parsed < 0 {
			writeError(w, http.StatusBadRequest, "bad_request", "invalid chain_id")
			return
		}
		chainID = parsed
	}

	typeFilter := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("type")))
	contracts, err := s.loadContractStatus(r.Context(), chainID, typeFilter)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "database_error", err.Error())
		return
	}

	s.roleMgr.LogOperatorEvent(r.Context(), "contracts_read", &s.chainID, operatorAddr, "contracts", "read", map[string]interface{}{"contract_count": len(contracts), "chain_id": chainID, "type": typeFilter}, ipAddr, r.UserAgent())

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

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

// HandleProtocolState handles GET /api/v1/track4/operator/protocol-state
func (s *Server) HandleProtocolState(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed")
		return
	}

	operatorAddr, ipAddr, ok := s.requireOperatorAccess(w, r)
	if !ok {
		return
	}

	state, err := s.loadProtocolState(r.Context())
	if err != nil {
		writeError(w, http.StatusInternalServerError, "database_error", err.Error())
		return
	}

	s.roleMgr.LogOperatorEvent(r.Context(), "protocol_state_read", &s.chainID, operatorAddr, "protocol/state", "read", map[string]interface{}{}, ipAddr, r.UserAgent())

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{"data": state})
}

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) requireOperatorAccess(w http.ResponseWriter, r *http.Request) (string, string, bool) {
	if s.db == nil {
		writeError(w, http.StatusServiceUnavailable, "service_unavailable", "database not configured")
		return "", "", false
	}

	operatorAddr := middleware.UserAddress(r.Context())
	operatorAddr = strings.TrimSpace(operatorAddr)
	if operatorAddr == "" {
		writeError(w, http.StatusUnauthorized, "unauthorized", "Operator address required")
		return "", "", false
	}

	ipAddr := clientIPAddress(r)
	whitelisted, err := s.roleMgr.IsIPWhitelisted(r.Context(), operatorAddr, ipAddr)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "database_error", err.Error())
		return "", "", false
	}
	if !whitelisted {
		writeError(w, http.StatusForbidden, "forbidden", "IP address not whitelisted")
		return "", "", false
	}

	return operatorAddr, ipAddr, true
}

func (s *Server) loadBridgeEvents(ctx context.Context, limit int) ([]map[string]interface{}, time.Time, error) {
	rows, err := s.db.Query(ctx, `
		SELECT event_type, operator_address, target_resource, action, details, COALESCE(ip_address::text, ''), COALESCE(user_agent, ''), timestamp
		FROM operator_events
		WHERE (chain_id = $1 OR chain_id IS NULL)
			AND (
				event_type ILIKE '%bridge%'
				OR target_resource ILIKE 'bridge%'
				OR target_resource ILIKE '%bridge%'
			)
		ORDER BY timestamp DESC
		LIMIT $2
	`, s.chainID, limit)
	if err != nil {
		return nil, time.Time{}, fmt.Errorf("failed to query bridge events: %w", err)
	}
	defer rows.Close()

	events := make([]map[string]interface{}, 0, limit)
	var latest time.Time
	for rows.Next() {
		var eventType, operatorAddress, targetResource, action, ipAddress, userAgent string
		var detailsBytes []byte
		var timestamp time.Time
		if err := rows.Scan(&eventType, &operatorAddress, &targetResource, &action, &detailsBytes, &ipAddress, &userAgent, &timestamp); err != nil {
			return nil, time.Time{}, fmt.Errorf("failed to scan bridge event: %w", err)
		}

		details := map[string]interface{}{}
		if len(detailsBytes) > 0 && string(detailsBytes) != "null" {
			_ = json.Unmarshal(detailsBytes, &details)
		}

		if latest.IsZero() {
			latest = timestamp
		}
		events = append(events, map[string]interface{}{
			"event_type":       eventType,
			"operator_address": operatorAddress,
			"target_resource":  targetResource,
			"action":           action,
			"details":          details,
			"ip_address":       ipAddress,
			"user_agent":       userAgent,
			"timestamp":        timestamp.UTC().Format(time.RFC3339),
		})
	}

	return events, latest, rows.Err()
}

func (s *Server) loadValidatorStatus(ctx context.Context) ([]map[string]interface{}, error) {
	rows, err := s.db.Query(ctx, `
		SELECT r.address, COALESCE(r.roles, '{}'), COALESCE(oe.last_seen, r.updated_at, r.approved_at), r.track_level
		FROM operator_roles r
		LEFT JOIN LATERAL (
			SELECT MAX(timestamp) AS last_seen
			FROM operator_events
			WHERE operator_address = r.address
		) oe ON TRUE
		WHERE r.approved = TRUE AND r.track_level >= 4
		ORDER BY COALESCE(oe.last_seen, r.updated_at, r.approved_at) DESC NULLS LAST, r.address
	`)
	if err != nil {
		return nil, fmt.Errorf("failed to query validator status: %w", err)
	}
	defer rows.Close()

	validators := make([]map[string]interface{}, 0)
	for rows.Next() {
		var address string
		var roles []string
		var lastSeen time.Time
		var trackLevel int
		if err := rows.Scan(&address, &roles, &lastSeen, &trackLevel); err != nil {
			return nil, fmt.Errorf("failed to scan validator row: %w", err)
		}

		roleScope := "operator"
		if inferred := inferOperatorScope(roles); inferred != "" {
			roleScope = inferred
		}

		row := map[string]interface{}{
			"address":     address,
			"status":      "active",
			"stake":       nil,
			"uptime":      nil,
			"last_block":  nil,
			"track_level": trackLevel,
			"roles":       roles,
			"role_scope":  roleScope,
		}
		if !lastSeen.IsZero() {
			row["last_seen"] = lastSeen.UTC().Format(time.RFC3339)
		}

		validators = append(validators, row)
	}

	return validators, rows.Err()
}

type contractRegistryEntry struct {
	Address string
	ChainID int
	Name    string
	Type    string
}

func (s *Server) loadContractStatus(ctx context.Context, chainID int, typeFilter string) ([]map[string]interface{}, error) {
	type contractRow struct {
		Name         string
		Status       string
		Compiler     string
		LastVerified *time.Time
	}

	dbRows := map[string]contractRow{}
	rows, err := s.db.Query(ctx, `
		SELECT LOWER(address), COALESCE(name, ''), verification_status, compiler_version, verified_at
		FROM contracts
		WHERE chain_id = $1
	`, chainID)
	if err != nil {
		return nil, fmt.Errorf("failed to query contracts: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var address string
		var row contractRow
		if err := rows.Scan(&address, &row.Name, &row.Status, &row.Compiler, &row.LastVerified); err != nil {
			return nil, fmt.Errorf("failed to scan contract row: %w", err)
		}
		dbRows[address] = row
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}

	registryEntries, err := loadContractRegistry(chainID)
	if err != nil {
		registryEntries = nil
	}

	seen := map[string]bool{}
	contracts := make([]map[string]interface{}, 0, len(registryEntries)+len(dbRows))
	appendRow := func(address, name, contractType, status, version string, lastVerified *time.Time) {
		if typeFilter != "" && contractType != typeFilter {
			return
		}
		row := map[string]interface{}{
			"address":  address,
			"chain_id": chainID,
			"type":     contractType,
			"name":     name,
			"status":   status,
		}
		if version != "" {
			row["version"] = version
		}
		if lastVerified != nil && !lastVerified.IsZero() {
			row["last_verified"] = lastVerified.UTC().Format(time.RFC3339)
		}
		contracts = append(contracts, row)
		seen[address] = true
	}

	for _, entry := range registryEntries {
		lowerAddress := strings.ToLower(entry.Address)
		dbRow, ok := dbRows[lowerAddress]
		status := "registry_only"
		version := ""
		name := entry.Name
		var lastVerified *time.Time
		if ok {
			if dbRow.Name != "" {
				name = dbRow.Name
			}
			status = dbRow.Status
			version = dbRow.Compiler
			lastVerified = dbRow.LastVerified
		}
		appendRow(lowerAddress, name, entry.Type, status, version, lastVerified)
	}

	for address, row := range dbRows {
		if seen[address] {
			continue
		}
		contractType := inferContractType(row.Name)
		appendRow(address, fallbackString(row.Name, address), contractType, row.Status, row.Compiler, row.LastVerified)
	}

	sort.Slice(contracts, func(i, j int) bool {
		left, _ := contracts[i]["name"].(string)
		right, _ := contracts[j]["name"].(string)
		if left == right {
			return contracts[i]["address"].(string) < contracts[j]["address"].(string)
		}
		return left < right
	})

	return contracts, nil
}

func (s *Server) loadProtocolState(ctx context.Context) (map[string]interface{}, error) {
	var totalBridged string
	var activeBridges int
	var lastBridgeAt *time.Time
	err := s.db.QueryRow(ctx, `
		SELECT
			COALESCE(SUM(amount)::text, '0'),
			COUNT(DISTINCT CONCAT(chain_from, ':', chain_to)),
			MAX(timestamp)
		FROM analytics_bridge_history
		WHERE status ILIKE 'success%'
			AND (chain_from = $1 OR chain_to = $1)
	`, s.chainID).Scan(&totalBridged, &activeBridges, &lastBridgeAt)
	if err != nil {
		return nil, fmt.Errorf("failed to query protocol state: %w", err)
	}

	registryEntries, _ := loadContractRegistry(s.chainID)
	bridgeEnabled := activeBridges > 0
	if !bridgeEnabled {
		for _, entry := range registryEntries {
			if entry.Type == "bridge" {
				bridgeEnabled = true
				break
			}
		}
	}

	protocolVersion := strings.TrimSpace(os.Getenv("EXPLORER_PROTOCOL_VERSION"))
	if protocolVersion == "" {
		protocolVersion = strings.TrimSpace(os.Getenv("PROTOCOL_VERSION"))
	}
	if protocolVersion == "" {
		protocolVersion = "unknown"
	}

	data := map[string]interface{}{
		"protocol_version": protocolVersion,
		"chain_id":         s.chainID,
		"config": map[string]interface{}{
			"bridge_enabled":                  bridgeEnabled,
			"max_transfer_amount":             nil,
			"max_transfer_amount_unavailable": true,
			"fee_structure":                   nil,
		},
		"state": map[string]interface{}{
			"total_locked":             nil,
			"total_locked_unavailable": true,
			"total_bridged":            totalBridged,
			"active_bridges":           activeBridges,
		},
	}

	if lastBridgeAt != nil && !lastBridgeAt.IsZero() {
		data["last_updated"] = lastBridgeAt.UTC().Format(time.RFC3339)
	} else {
		data["last_updated"] = time.Now().UTC().Format(time.RFC3339)
	}

	return data, nil
}

func loadContractRegistry(chainID int) ([]contractRegistryEntry, error) {
	chainKey := strconv.Itoa(chainID)
	candidates := []string{}
	if env := strings.TrimSpace(os.Getenv("SMART_CONTRACTS_MASTER_JSON")); env != "" {
		candidates = append(candidates, env)
	}
	candidates = append(candidates,
		"config/smart-contracts-master.json",
		"../config/smart-contracts-master.json",
		"../../config/smart-contracts-master.json",
		filepath.Join("explorer-monorepo", "config", "smart-contracts-master.json"),
	)

	var raw []byte
	for _, candidate := range candidates {
		if strings.TrimSpace(candidate) == "" {
			continue
		}
		body, err := os.ReadFile(candidate)
		if err == nil && len(body) > 0 {
			raw = body
			break
		}
	}
	if len(raw) == 0 {
		return nil, fmt.Errorf("smart-contracts-master.json not found")
	}

	var root struct {
		Chains map[string]struct {
			Contracts map[string]string `json:"contracts"`
		} `json:"chains"`
	}
	if err := json.Unmarshal(raw, &root); err != nil {
		return nil, fmt.Errorf("failed to parse contract registry: %w", err)
	}

	chain, ok := root.Chains[chainKey]
	if !ok {
		return nil, nil
	}

	entries := make([]contractRegistryEntry, 0, len(chain.Contracts))
	for name, address := range chain.Contracts {
		addr := strings.TrimSpace(address)
		if addr == "" {
			continue
		}
		entries = append(entries, contractRegistryEntry{
			Address: addr,
			ChainID: chainID,
			Name:    name,
			Type:    inferContractType(name),
		})
	}

	sort.Slice(entries, func(i, j int) bool {
		if entries[i].Name == entries[j].Name {
			return strings.ToLower(entries[i].Address) < strings.ToLower(entries[j].Address)
		}
		return entries[i].Name < entries[j].Name
	})

	return entries, nil
}

func inferOperatorScope(roles []string) string {
	for _, role := range roles {
		lower := strings.ToLower(role)
		switch {
		case strings.Contains(lower, "validator"):
			return "validator"
		case strings.Contains(lower, "sequencer"):
			return "sequencer"
		case strings.Contains(lower, "bridge"):
			return "bridge"
		}
	}
	return ""
}

func inferContractType(name string) string {
	lower := strings.ToLower(name)
	switch {
	case strings.Contains(lower, "bridge"):
		return "bridge"
	case strings.Contains(lower, "router"):
		return "router"
	case strings.Contains(lower, "pool"), strings.Contains(lower, "pmm"), strings.Contains(lower, "amm"):
		return "liquidity"
	case strings.Contains(lower, "oracle"):
		return "oracle"
	case strings.Contains(lower, "vault"):
		return "vault"
	case strings.Contains(lower, "token"), strings.Contains(lower, "weth"), strings.Contains(lower, "cw"), strings.Contains(lower, "usdt"), strings.Contains(lower, "usdc"):
		return "token"
	default:
		return "contract"
	}
}

func fallbackString(value, fallback string) string {
	if strings.TrimSpace(value) == "" {
		return fallback
	}
	return value
}
