package auth

import (
	"context"
	"fmt"
	"strings"

	"github.com/jackc/pgx/v5/pgxpool"
)

// InstitutionalTier represents a DBIS institutional membership tier.
// These are the canonical tiers from d-bis.org/members#tiers.
type InstitutionalTier string

const (
	TierSovereignCentralBank InstitutionalTier = "sovereign_central_bank"
	TierGlobalFamilyOffice   InstitutionalTier = "global_family_office"
	TierSettlementMember     InstitutionalTier = "settlement_member"
	TierInfrastructureOp     InstitutionalTier = "infrastructure_operator"
	TierOversightJudicial    InstitutionalTier = "oversight_judicial"
	TierDelegatedAuthority   InstitutionalTier = "delegated_authority"
	TierStandardsBody        InstitutionalTier = "standards_body"
)

// InstitutionalTierLabel returns the human-readable label for a tier.
func InstitutionalTierLabel(t InstitutionalTier) string {
	switch t {
	case TierSovereignCentralBank:
		return "Sovereign Central Bank"
	case TierGlobalFamilyOffice:
		return "Global Family Office"
	case TierSettlementMember:
		return "Settlement Member"
	case TierInfrastructureOp:
		return "Infrastructure Operator"
	case TierOversightJudicial:
		return "Oversight & Judicial"
	case TierDelegatedAuthority:
		return "Delegated Authority"
	case TierStandardsBody:
		return "Standards Body"
	default:
		return string(t)
	}
}

// DefaultTrackForTier maps an institutional membership tier to the default
// explorer access track. Higher tracks inherit all lower-track permissions.
//
//	Track 1 — Public explorer (read blocks, txs, basic address)
//	Track 2 — Enhanced explorer (full address, tokens, tx history, search)
//	Track 3 — Analytics (flows, bridge analytics, risk, distribution)
//	Track 4 — Operator (bridge control, validators, protocol config)
func DefaultTrackForTier(tier InstitutionalTier) int {
	switch tier {
	case TierSovereignCentralBank:
		return 3 // analytics access; operator granted per-address
	case TierGlobalFamilyOffice:
		return 3
	case TierSettlementMember:
		return 2
	case TierInfrastructureOp:
		return 4
	case TierOversightJudicial:
		return 3
	case TierDelegatedAuthority:
		return 3
	case TierStandardsBody:
		return 2
	default:
		return 1
	}
}

// InstitutionalMember represents an entity in the DBIS member directory.
type InstitutionalMember struct {
	ID           int               `json:"id"`
	Slug         string            `json:"slug"`
	Abbreviation string            `json:"abbreviation"`
	Name         string            `json:"name"`
	Tier         InstitutionalTier `json:"tier"`
	Description  string            `json:"description"`
	Jurisdiction string            `json:"jurisdiction,omitempty"`
	LEI          string            `json:"lei,omitempty"`
	Latitude     float64           `json:"latitude,omitempty"`
	Longitude    float64           `json:"longitude,omitempty"`
	MapLabel     string            `json:"map_label,omitempty"`
	Active       bool              `json:"active"`
}

// MembershipStore provides read/write access to the institutional members table.
type MembershipStore struct {
	db *pgxpool.Pool
}

// NewMembershipStore creates a new MembershipStore.
func NewMembershipStore(db *pgxpool.Pool) *MembershipStore {
	return &MembershipStore{db: db}
}

func isMissingMembershipTableError(err error) bool {
	return err != nil && strings.Contains(err.Error(), `relation "institutional_members" does not exist`)
}

// ListMembers returns all active institutional members.
func (s *MembershipStore) ListMembers(ctx context.Context) ([]InstitutionalMember, error) {
	rows, err := s.db.Query(ctx, `
		SELECT id, slug, abbreviation, name, tier, description,
		       COALESCE(jurisdiction, ''), COALESCE(lei, ''),
		       COALESCE(latitude, 0), COALESCE(longitude, 0),
		       COALESCE(map_label, ''), active
		FROM institutional_members
		WHERE active = TRUE
		ORDER BY tier, name
	`)
	if err != nil {
		if isMissingMembershipTableError(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("list members: %w", err)
	}
	defer rows.Close()

	var members []InstitutionalMember
	for rows.Next() {
		var m InstitutionalMember
		if err := rows.Scan(
			&m.ID, &m.Slug, &m.Abbreviation, &m.Name, &m.Tier, &m.Description,
			&m.Jurisdiction, &m.LEI, &m.Latitude, &m.Longitude, &m.MapLabel, &m.Active,
		); err != nil {
			return nil, fmt.Errorf("scan member: %w", err)
		}
		members = append(members, m)
	}
	return members, rows.Err()
}

// GetMemberBySlug returns a single member by URL slug.
func (s *MembershipStore) GetMemberBySlug(ctx context.Context, slug string) (*InstitutionalMember, error) {
	var m InstitutionalMember
	err := s.db.QueryRow(ctx, `
		SELECT id, slug, abbreviation, name, tier, description,
		       COALESCE(jurisdiction, ''), COALESCE(lei, ''),
		       COALESCE(latitude, 0), COALESCE(longitude, 0),
		       COALESCE(map_label, ''), active
		FROM institutional_members
		WHERE slug = $1
	`, slug).Scan(
		&m.ID, &m.Slug, &m.Abbreviation, &m.Name, &m.Tier, &m.Description,
		&m.Jurisdiction, &m.LEI, &m.Latitude, &m.Longitude, &m.MapLabel, &m.Active,
	)
	if err != nil {
		if isMissingMembershipTableError(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("get member by slug: %w", err)
	}
	return &m, nil
}

// GetMemberByAddress looks up the institutional member linked to a wallet
// address via the institutional_member_wallets junction table.
func (s *MembershipStore) GetMemberByAddress(ctx context.Context, address string) (*InstitutionalMember, error) {
	var m InstitutionalMember
	err := s.db.QueryRow(ctx, `
		SELECT m.id, m.slug, m.abbreviation, m.name, m.tier, m.description,
		       COALESCE(m.jurisdiction, ''), COALESCE(m.lei, ''),
		       COALESCE(m.latitude, 0), COALESCE(m.longitude, 0),
		       COALESCE(m.map_label, ''), m.active
		FROM institutional_members m
		JOIN institutional_member_wallets w ON w.member_id = m.id
		WHERE LOWER(w.address) = LOWER($1) AND w.active = TRUE AND m.active = TRUE
	`, address).Scan(
		&m.ID, &m.Slug, &m.Abbreviation, &m.Name, &m.Tier, &m.Description,
		&m.Jurisdiction, &m.LEI, &m.Latitude, &m.Longitude, &m.MapLabel, &m.Active,
	)
	if err != nil {
		if isMissingMembershipTableError(err) || strings.Contains(err.Error(), "no rows") {
			return nil, nil
		}
		return nil, fmt.Errorf("get member by address: %w", err)
	}
	return &m, nil
}

// ListTiers returns the canonical set of institutional membership tiers
// with their labels and default access tracks.
func ListTiers() []struct {
	Tier         InstitutionalTier `json:"tier"`
	Label        string            `json:"label"`
	DefaultTrack int               `json:"default_track"`
} {
	tiers := []InstitutionalTier{
		TierSovereignCentralBank,
		TierGlobalFamilyOffice,
		TierSettlementMember,
		TierInfrastructureOp,
		TierOversightJudicial,
		TierDelegatedAuthority,
		TierStandardsBody,
	}
	result := make([]struct {
		Tier         InstitutionalTier `json:"tier"`
		Label        string            `json:"label"`
		DefaultTrack int               `json:"default_track"`
	}, len(tiers))
	for i, t := range tiers {
		result[i].Tier = t
		result[i].Label = InstitutionalTierLabel(t)
		result[i].DefaultTrack = DefaultTrackForTier(t)
	}
	return result
}
