// tslint:disable:no-console
import * as moment from 'moment-timezone';
import * as mongoose from 'mongoose';
import * as redis from 'redis';
import { chevre } from '../../../../lib/index';

const today = moment()
    .tz('Asia/Tokyo')
    .format('YYYYMMDD');
const project = { id: String(process.env.PROJECT_ID) };
const providerId = 'sampleProviderId';
const eventId = `sampleEventId${today}:03`;
const eventStartDate = new Date('2025-05-01T00:00:00Z');
const expires = moment(eventStartDate)
    .add(1, 'week')
    .toDate();
const seatSection = 'SampleSectionNameXXXXXXXXXXXXXXXXXXX';
// tslint:disable-next-line:no-magic-numbers prefer-array-literal
const allSeatNumbers = [...Array(10000)].map((__, seatKey) => `SampleSeatNumber-${seatKey}`);
const OPERATION_INTERVAL = 2000;
// const MAXIMUM_CAPACITY = 10002;

mongoose.Model.on('index', (...args) => {
    console.error('******** index event emitted. ********\n', args);
});

async function sleep(waitTime: number): Promise<void> {
    return new Promise<void>((resolve) => {
        setTimeout(
            () => {
                resolve();
            },
            waitTime
        );
    });
}

const client = redis.createClient<redis.RedisDefaultModules, Record<string, never>, Record<string, never>>({
    socket: {
        port: Number(<string>process.env.REDIS_PORT),
        host: <string>process.env.REDIS_HOST
    },
    password: <string>process.env.REDIS_KEY
})
    .on('error', (err) => {
        // eslint-disable-next-line no-console
        console.error('createDefaultRedisClient: client onError:', err);
        // reject(err);
    });
client.connect();
mongoose.connect(<string>process.env.MONGOLAB_URI, { autoIndex: false });

const formatter = new Intl.NumberFormat('ja-JP');
// => "1,000"

// tslint:disable-next-line:max-func-body-length
async function lockSeatsForcibly(params: {
    maximumCapacity?: number;
}) {
    const { maximumCapacity } = params;
    let startTime: [number, number] = process.hrtime();
    let diff: [number, number] = process.hrtime(startTime);

    const stockHolderRepo = await chevre.repository.StockHolder.createInstance(
        client,
        mongoose.connection
    );

    startTime = process.hrtime();
    console.log('counting unavailableOffers...');
    let countUnavailableOffersResult = await stockHolderRepo.countUnavailableOffers({
        project: { id: project.id },
        event: {
            id: eventId,
            startDate: eventStartDate,
            hasTicketedSeat: true
        }
    });
    console.log('countUnavailableOffersResult:', countUnavailableOffersResult);
    console.log('diff:', [diff[0], formatter.format(diff[1])]);

    await sleep(OPERATION_INTERVAL);
    startTime = process.hrtime();
    console.log('counting searching holders...');
    const searchHoldersResult = await stockHolderRepo.searchHolders({
        project: { id: project.id },
        eventId,
        startDate: eventStartDate,
        hasTicketedSeat: true,
        offers: [
            // tslint:disable-next-line:no-magic-numbers
            ...allSeatNumbers.slice(0, 100)
                .map((seatNumber) => ({ seatSection, seatNumber }))
        ]
    });
    diff = process.hrtime(startTime);
    console.log('searchHoldersResult.length:', searchHoldersResult.length);
    console.log('diff:', [diff[0], formatter.format(diff[1])]);

    // select 2 seats
    // tslint:disable-next-line:insecure-random
    const seatNumber1 = allSeatNumbers[Math.floor(allSeatNumbers.length * Math.random())];
    const seatNumber2 = allSeatNumbers.filter((seatNumber) => seatNumber !== seatNumber1)[
        // tslint:disable-next-line:insecure-random
        Math.floor((allSeatNumbers.length - 1) * Math.random())
    ];

    const seatNumbers = [seatNumber1, seatNumber2];
    console.log('seats selected.', seatNumbers);

    let locked = false;
    while (!locked) {
        await sleep(OPERATION_INTERVAL);
        startTime = process.hrtime();
        const currentHolders: string[] = [];
        for (const seatNumber of seatNumbers) {
            const currentHolder = await stockHolderRepo.getHolder({
                project: { id: project.id },
                eventId,
                startDate: eventStartDate,
                hasTicketedSeat: true,
                offer: { seatSection, seatNumber }
            });
            if (typeof currentHolder === 'string') {
                currentHolders.push(currentHolder);
            }
        }
        diff = process.hrtime(startTime);
        console.log('currentHolders:', currentHolders);
        console.log('diff:', [diff[0], formatter.format(diff[1])]);

        try {
            await sleep(OPERATION_INTERVAL);
            const newHolder = Date.now()
                .toString();
            const bookingTime = new Date();
            startTime = process.hrtime();
            console.log('locking...', newHolder, seatSection, seatNumbers);
            if (typeof maximumCapacity === 'number') {
                try {
                    await stockHolderRepo.lockIfNotLimitExceeded(
                        {
                            project: { id: project.id },
                            provider: { id: providerId },
                            eventId,
                            startDate: eventStartDate,
                            hasTicketedSeat: true,
                            offers: seatNumbers.map((seatNumber) => ({ seatSection, seatNumber })),
                            expires,
                            holder: newHolder,
                            bookingTime
                        },
                        maximumCapacity
                    );
                } catch (error) {
                    if (error.message === 'maximumAttendeeCapacity exceeded') {
                        // ok
                    } else {
                        throw error;
                    }
                }
            } else {
                await stockHolderRepo.lock({
                    project: { id: project.id },
                    provider: { id: providerId },
                    eventId,
                    startDate: eventStartDate,
                    hasTicketedSeat: true,
                    offers: seatNumbers.map((seatNumber) => ({ seatSection, seatNumber })),
                    expires,
                    holder: newHolder,
                    bookingTime
                });
            }
            diff = process.hrtime(startTime);
            console.log('locked.', newHolder);
            console.log('diff:', [diff[0], formatter.format(diff[1])]);
            locked = true;
        } catch (error) {
            if (error instanceof chevre.factory.errors.AlreadyInUse) {
                console.log('lockResult:', error.name, error.message);
            } else {
                console.error(error);
            }
        }

        await sleep(OPERATION_INTERVAL);
        for (const currentHolder of currentHolders) {
            for (const seatNumber of seatNumbers) {
                startTime = process.hrtime();
                console.log('unlocking...', currentHolder, seatSection, seatNumber);
                const unlockResult = await stockHolderRepo.unlock({
                    project: { id: project.id },
                    eventId,
                    startDate: eventStartDate,
                    hasTicketedSeat: true,
                    offer: { seatSection, seatNumber },
                    holder: currentHolder
                });
                diff = process.hrtime(startTime);
                console.log('unlockResult:', unlockResult);
                console.log('diff:', [diff[0], formatter.format(diff[1])]);
            }
        }
    }

    await sleep(OPERATION_INTERVAL);
    countUnavailableOffersResult = await stockHolderRepo.countUnavailableOffers({
        project: { id: project.id },
        event: {
            id: eventId,
            startDate: eventStartDate,
            hasTicketedSeat: true
        }
    });
    console.log('countUnavailableOffersResult:', countUnavailableOffersResult);

}

async function main() {
    // tslint:disable-next-line:no-magic-numbers
    const numLock = (typeof process.argv[2] === 'string') ? Number(process.argv[2]) : 1;
    // tslint:disable-next-line:no-magic-numbers
    const maximumCapacity = (typeof process.argv[3] === 'string') ? Number(process.argv[3]) : undefined;

    const LOCK_INTERVAL = 300;
    let i = 0;
    let lockedCount = 0;
    let timeout: NodeJS.Timeout;
    const processStartDate = new Date();
    function onSeatsLocked() {
        lockedCount += 1;
        console.log(lockedCount, 'lockSeatsForcibly executed!');
        console.log('processed.', processStartDate, '~', new Date());
    }

    timeout = setInterval(
        () => {
            if (i >= numLock) {
                clearInterval(timeout);

                return;
            }

            i += 1;
            lockSeatsForcibly({ maximumCapacity })
                .then(onSeatsLocked)
                .catch(console.error);
        },
        LOCK_INTERVAL
    );
}

main()
    .then()
    .catch(console.error);
