import { AnchorProvider, Program } from '@coral-xyz/anchor';
import { PublicKey } from '@solana/web3.js';

import { SolanaProgramService } from '../program';
import type { ValidatorLeaderboard } from '../../../assets/solana/types/validator_leaderboard';
import {
    deriveLeaderboardStatePda,
    deriveValidatorRecordPda,
} from '../constants';
import { LeaderboardState, ValidatorRecord } from '../types';

/**
 * Simple read client for the validator_leaderboard program.
 *
 * Adjust account names/fields to match your IDL (state, validatorRecord, etc).
 */
export class LeaderboardClient {
    private program: Program<ValidatorLeaderboard>;

    constructor(private provider: AnchorProvider) {
        const svc = new SolanaProgramService(provider);
        this.program = svc.getProgram('validatorLeaderboard');
    }

    async getState(): Promise<LeaderboardState | null> {
        const pda = deriveLeaderboardStatePda();
        try {
            // Assumes account name "leaderboardState"
            return await this.program.account.leaderboardState.fetch(pda);
        } catch {
            return null;
        }
    }

    async getValidatorRecord(voteAccount: PublicKey): Promise<ValidatorRecord | null> {
        const pda = deriveValidatorRecordPda(voteAccount);
        try {
            // Assumes account name "validatorRecord"
            return await this.program.account.validatorRecord.fetchNullable(pda);
        } catch {
            return null;
        }
    }

    /**
     * Convenience helper to fetch and return top validators ordered
     * by the on-chain leaderboard scores.
     *
     * Uses:
     *  - `leaderboardState.scores`
     *  - `leaderboardState.sortedIndices`
     *  - `validatorRecord.registryIndex`
     *
     * Fallback: if leaderboardState can't be fetched, we sort by
     * `creditsObserved` descending.
     */
    async getTopValidators(limit = 20): Promise<ValidatorRecord[]> {
        // Try to use the leaderboard state first
        try {
            const leaderboardPda = deriveLeaderboardStatePda();
            const leaderboard: LeaderboardState 
                = await this.program.account.leaderboardState.fetch(leaderboardPda);

            const all = await this.program.account.validatorRecord.all();
            const byIndex = new Map<number, ValidatorRecord>();

            for (const r of all) {
                const vr = r.account;
                byIndex.set(vr.registryIndex, vr);
            }

            const max = Math.min(
                limit,
                leaderboard.numValidators,
                leaderboard.sortedIndices.length,
            );

            const top: ValidatorRecord[] = [];
            for (let i = 0; i < max; i++) {
                const idx = leaderboard.sortedIndices[i];
                const rec = byIndex.get(idx);
                if (rec) {
                    top.push(rec);
                }
            }

            return top;
        } catch (e) {
            // Fallback: no leaderboardState available, sort by creditsObserved.
            const all = await this.program.account.validatorRecord.all();

            all.sort((a, b) => {
                const va = a.account;
                const vb = b.account;
                const ca = va.creditsObserved;
                const cb = vb.creditsObserved;
                // BN.cmp: -1 if a < b, 0 if equal, 1 if a > b
                return cb.cmp(ca);
            });

            return all.slice(0, limit).map(r => r.account);
        }
    }
}