import { stringToBuffer } from '../utils/StringToBuffer.js';
import type {
    IChallengeSolution,
    IChallengeSubmission,
    IChallengeVerification,
    RawChallenge,
    RawChallengeSubmission,
    RawChallengeVerification,
} from './interfaces/IChallengeSolution.js';
import { Address } from '../keypair/Address.js';
import { EpochValidator } from './validator/EpochValidator.js';
import { BinaryWriter } from '../buffer/BinaryWriter.js';
import { MessageSigner } from '../keypair/MessageSigner.js';
import { toHex } from '@btc-vision/bitcoin';

export class ChallengeVerification implements IChallengeVerification {
    public readonly epochHash: Uint8Array;
    public readonly epochRoot: Uint8Array;
    public readonly targetHash: Uint8Array;
    public readonly targetChecksum: Uint8Array;
    public readonly startBlock: bigint;
    public readonly endBlock: bigint;
    public readonly proofs: readonly Uint8Array[];

    constructor(data: RawChallengeVerification) {
        this.epochHash = stringToBuffer(data.epochHash);
        this.epochRoot = stringToBuffer(data.epochRoot);
        this.targetHash = stringToBuffer(data.targetHash);
        this.targetChecksum = stringToBuffer(data.targetChecksum);
        this.startBlock = BigInt(data.startBlock);
        this.endBlock = BigInt(data.endBlock);
        this.proofs = Object.freeze(data.proofs.map((proof) => stringToBuffer(proof)));
    }
}

export class ChallengeSubmission implements IChallengeSubmission {
    public readonly publicKey: Address;
    public readonly solution: Uint8Array;
    public readonly graffiti: Uint8Array | undefined;
    public readonly signature: Uint8Array;

    constructor(
        data: RawChallengeSubmission,
        public readonly epochNumber: bigint,
    ) {
        this.publicKey = Address.fromString(data.mldsaPublicKey, data.legacyPublicKey);
        this.solution = stringToBuffer(data.solution);
        this.graffiti = data.graffiti ? stringToBuffer(data.graffiti) : undefined;
        this.signature = stringToBuffer(data.signature);
    }

    public verifySignature(): boolean {
        const signatureDataWriter = new BinaryWriter();
        signatureDataWriter.writeAddress(this.publicKey);
        signatureDataWriter.writeU64(this.epochNumber);
        signatureDataWriter.writeBytes(this.solution);

        if (this.graffiti) {
            signatureDataWriter.writeBytes(this.graffiti);
        }

        const buffer = signatureDataWriter.getBuffer();
        return MessageSigner.verifySignature(
            this.publicKey.tweakedPublicKeyToBuffer(),
            buffer,
            this.signature,
        );
    }
}

export class ChallengeSolution implements IChallengeSolution {
    public readonly epochNumber: bigint;
    public readonly publicKey: Address;
    public readonly solution: Uint8Array;
    public readonly salt: Uint8Array;
    public readonly graffiti: Uint8Array;
    public readonly difficulty: number;
    public readonly verification: ChallengeVerification;

    private readonly submission?: ChallengeSubmission | undefined;

    constructor(data: RawChallenge) {
        this.epochNumber = BigInt(data.epochNumber);
        this.publicKey = Address.fromString(data.mldsaPublicKey, data.legacyPublicKey);
        this.solution = stringToBuffer(data.solution);
        this.salt = stringToBuffer(data.salt);
        this.graffiti = stringToBuffer(data.graffiti);
        this.difficulty = data.difficulty;
        this.verification = new ChallengeVerification(data.verification);
        this.submission = data.submission
            ? new ChallengeSubmission(data.submission, this.epochNumber + 2n)
            : data.submission;
    }

    /**
     * Static method to validate from raw data directly
     */
    public static validateRaw(data: RawChallenge): boolean {
        return EpochValidator.validateEpochWinner(data);
    }

    public verifySubmissionSignature(): boolean {
        if (!this.submission) {
            throw new Error('No submission provided in request.');
        }

        return this.submission.verifySignature();
    }

    public getSubmission(): ChallengeSubmission | undefined {
        if (!this.submission) {
            return;
        }

        if (!this.verifySubmissionSignature()) {
            throw new Error('Invalid submission signature.');
        }

        return this.submission;
    }

    /**
     * Verify this challenge
     * @returns {boolean} True if the challenge is valid
     */
    public verify(): boolean {
        return EpochValidator.validateChallengeSolution(this);
    }

    /**
     * Get the preimage challenge
     * @returns {Uint8Array} The solution/challenge as a Uint8Array
     */
    public toBuffer(): Uint8Array {
        return this.solution;
    }

    /**
     * Get the solution as a hex string
     * @returns {string} The solution as a hex string with 0x prefix
     */
    public toHex(): string {
        return '0x' + toHex(this.solution);
    }

    /**
     * Convert to raw format for serialization
     */
    public toRaw(): RawChallenge {
        return {
            epochNumber: this.epochNumber.toString(),
            mldsaPublicKey: this.publicKey.toHex(),
            legacyPublicKey: this.publicKey.tweakedToHex(),
            solution: this.toHex(),
            salt: '0x' + toHex(this.salt),
            graffiti: '0x' + toHex(this.graffiti),
            difficulty: this.difficulty,
            verification: {
                epochHash: '0x' + toHex(this.verification.epochHash),
                epochRoot: '0x' + toHex(this.verification.epochRoot),
                targetHash: '0x' + toHex(this.verification.targetHash),
                targetChecksum: '0x' + toHex(this.verification.targetChecksum),
                startBlock: this.verification.startBlock.toString(),
                endBlock: this.verification.endBlock.toString(),
                proofs: this.verification.proofs.map((p) => '0x' + toHex(p)),
            },
        };
    }

    /**
     * Calculate the expected solution hash for this challenge
     * @returns {Uint8Array} The calculated solution hash
     */
    public calculateSolution(): Uint8Array {
        return EpochValidator.calculateSolution(
            this.verification.targetChecksum,
            this.publicKey.toBuffer(),
            this.salt,
        );
    }

    /**
     * Check if the challenge meets a specific difficulty requirement
     * @param {number} minDifficulty The minimum difficulty required
     * @returns {Promise<{valid: boolean; difficulty: number}>} Validation result
     */
    public checkDifficulty(minDifficulty: number): { valid: boolean; difficulty: number } {
        return EpochValidator.checkDifficulty(
            this.solution,
            this.verification.targetHash,
            minDifficulty,
        );
    }

    /**
     * Get the mining target block for this epoch
     * @returns {bigint | null} The target block number or null if epoch 0
     */
    public getMiningTargetBlock(): bigint | null {
        return EpochValidator.getMiningTargetBlock(this.epochNumber);
    }
}
