import { BadRequestException, Injectable, InternalServerErrorException, NotFoundException, OnModuleDestroy, OnModuleInit } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { PasskeyCredential } from "../entity/passkey-credential.entity";
import { Repository } from "typeorm";
import { User } from "src/user/entities/user.entity";
import { randomBytes } from 'crypto';
import { generateAuthenticationOptions, GenerateAuthenticationOptionsOpts, generateRegistrationOptions, GenerateRegistrationOptionsOpts, VerifiedAuthenticationResponse, VerifiedRegistrationResponse, verifyAuthenticationResponse, verifyRegistrationResponse } from "@simplewebauthn/server";
import { isoBase64URL } from '@simplewebauthn/server/helpers';

interface ChallengeEntry {
    value: string;
    expiresAt: number;
}

class MemoryChallengeStore {
    private store = new Map<string, ChallengeEntry>();
    private cleanupInterval: NodeJS.Timeout | null = null;

    constructor(private ttlMs: number = 5 * 60 * 100) {
        this.startCleanup();
    }

    set(key: string, value: string): void {
        this.store.set(key, {
            value,
            expiresAt: Date.now() + this.ttlMs,
        });
    }

    get(key: string): string | null {
        const entry = this.store.get(key);
        if (!entry) return null;
        if (Date.now() > entry.expiresAt) {
            this.store.delete(key);
            return null;
        }
        return entry.value;
    }

    delete(key: string): void {
        this.store.delete(key);
    }

    private startCleanup(): void {
        this.cleanupInterval = setInterval(() => {
            const now = Date.now();
            for (const [key, entry] of this.store.entries()) {
                if (now > entry.expiresAt) {
                    this.store.delete(key);
                }
            }
        }, 60_000); // 每分钟清理一次
    }

    stopCleanup(): void {
        if (this.cleanupInterval) {
            clearInterval(this.cleanupInterval);
            this.cleanupInterval = null;
        }
    }
}

const registrationChallenges = new MemoryChallengeStore(5 * 60 * 1000); // 5 分钟过期
const authenticationChallenges = new MemoryChallengeStore(5 * 60 * 1000);


@Injectable()
export class PasskeyService implements OnModuleDestroy {

    private readonly rpID: string;
    private readonly origin: string;
    private readonly rpName: string;

    constructor(
        @InjectRepository(PasskeyCredential)
        private readonly passkeyRepo: Repository<PasskeyCredential>,
        @InjectRepository(User)
        private readonly userRepository: Repository<User>,
    ) {
        this.rpID = process.env.WEBAUTHN_RP_ID;
        this.origin = process.env.WEBAUTHN_ORIGIN;
        this.rpName = process.env.WEBAUTHN_RP_NAME;

        if (!this.rpID || !this.origin || !this.rpName) {
            throw new Error('Missing required env: WEBAUTHN_RP_ID or WEBAUTHN_ORIGIN');
        }
    }

    onModuleDestroy() {
        registrationChallenges.stopCleanup();
        authenticationChallenges.stopCleanup();
    }

    private generateChallenge(length: number = 32): string {
        return randomBytes(length).toString('base64');
    }

    async getRegistrationOptions(userId: string) {
        const user = await this.userRepository.findOneBy({ userId });
        if (!user) {
            throw new NotFoundException('用户不存在');
        }

        const challenge = this.generateChallenge();

        const opts: GenerateRegistrationOptionsOpts = {
            rpName: this.rpName,
            rpID: this.rpID,
            userID: Buffer.from(userId),
            userName: user.username || 'user',
            userDisplayName: user.nickname || 'User',
            challenge,
            authenticatorSelection: {
                residentKey: 'required', // 必须是可发现凭证（Passkey）
                userVerification: 'preferred',
            },
            supportedAlgorithmIDs: [-7], // ES256
            timeout: 60000,
        };

        const options = await generateRegistrationOptions(opts);
        registrationChallenges.set(userId, options.challenge)
        return options;
    }

    async register(userId: string, credentialResponse: any, name: string): Promise<PasskeyCredential> {
        const expectedChallenge = registrationChallenges.get(userId);
        if (!expectedChallenge) {
            throw new BadRequestException('注册失败，请重试');
        }

        let verification: VerifiedRegistrationResponse;
        try {
            verification = await verifyRegistrationResponse({
                response: credentialResponse,
                expectedChallenge,
                expectedOrigin: this.origin,
                expectedRPID: this.rpID,
                requireUserVerification: false,
            });
        } catch (err) {
            throw new BadRequestException('注册失败');
        }

        if (!verification.verified) {
            throw new BadRequestException('注册失败');
        }

        const { credential } = verification.registrationInfo;
        if (!credential) {
            throw new InternalServerErrorException('服务器内部错误');
        }

        // 保存凭证到数据库
        const passkey = this.passkeyRepo.create({
            user: { userId } as User,
            name: name || '新的通行证',
            credentialId: credential.id,
            publicKey: isoBase64URL.fromBuffer(credential.publicKey),
            signCount: credential.counter,
            verified: true,
        });

        await this.passkeyRepo.save(passkey);
        registrationChallenges.delete(userId);

        return passkey;
    }

    async getAuthenticationOptions(sessionId: string) {
        const challenge = this.generateChallenge();
        const opts: GenerateAuthenticationOptionsOpts = {
            rpID: this.rpID,
            challenge,
            timeout: 60000,
            userVerification: 'preferred',
        };

        const options = await generateAuthenticationOptions(opts);
        authenticationChallenges.set(sessionId, options.challenge);
        return options;
    }

    async login(sessionId: string, credentialResponse: any): Promise<User> {
        const expectedChallenge = authenticationChallenges.get(sessionId);
        if (!expectedChallenge) {
            throw new BadRequestException('认证失败，请重试');
        }

        const credentialId = credentialResponse.id;
        const passkey = await this.passkeyRepo.findOne({
            where: { credentialId, verified: true },
            relations: ['user'],
        });

        if (!passkey) {
            throw new NotFoundException('未找到可用的通行证');
        }

        let verification: VerifiedAuthenticationResponse;
        try {
            verification = await verifyAuthenticationResponse({
                response: credentialResponse,
                expectedChallenge,
                expectedOrigin: this.origin,
                expectedRPID: this.rpID,
                credential: {
                    id: passkey.credentialId,
                    publicKey: isoBase64URL.toBuffer(passkey.publicKey),
                    counter: passkey.signCount,
                },
                requireUserVerification: false,
            });
        } catch (err) {
            throw new BadRequestException('认证失败');
        }

        if (!verification.verified) {
            throw new BadRequestException('认证失败');
        }

        const newSignCount = verification.authenticationInfo.newCounter;
        if (newSignCount !== passkey.signCount) {
            passkey.signCount = newSignCount;
            await this.passkeyRepo.save(passkey);
        }

        authenticationChallenges.delete(sessionId);
        return passkey.user;
    }

    async listUserPasskeys(userId: string): Promise<PasskeyCredential[]> {
        return this.passkeyRepo.find({
            where: { user: { userId }, verified: true },
            select: ['id', 'name', 'createdAt'],
        });
    }

    async removePasskey(userId: string, passkeyId: string): Promise<void> {
        const result = await this.passkeyRepo.delete({
            id: passkeyId,
            user: { userId },
        });

        if (result.affected === 0) {
            throw new NotFoundException('未找到对应的通行证');
        }
    }
}