import { IsInt, IsOptional, IsPositive } from 'class-validator';
import { BadRequestError } from 'routing-controllers';
import { getConnection } from 'typeorm';
import { RunnerCardNotFoundError } from '../../../errors/RunnerCardErrors';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { ScanStationNotFoundError } from '../../../errors/ScanStationErrors';
import { RunnerCard } from '../../entities/RunnerCard';
import { ScanStation } from '../../entities/ScanStation';
import { TrackScan } from '../../entities/TrackScan';

/**
 * This classed is used to create a new Scan entity from a json body (post request).
 */
export class CreateTrackScan {
    /**
     * The id of the runnerCard associated with the scan.
     * This get's saved for documentation and management purposes.
     */
    @IsInt()
    @IsPositive()
    card: number;

    /**
     * The scanning station's id that created the scan.
     * Mainly used for logging and traceing back scans (or errors).
     * You don't have to provide the station if you're authenticateing via a scanstation token (The server takes care of it for you).
     */
    @IsInt()
    @IsPositive()
    @IsOptional()
    station?: number;

    /**
     * Creates a new Track entity from this.
     */
    public async toEntity(): Promise<TrackScan> {
        let newScan: TrackScan = new TrackScan();

        newScan.station = await this.getStation();
        newScan.card = await this.getCard();

        newScan.track = newScan.station.track;
        newScan.runner = newScan.card.runner;

        if (!newScan.runner) {
            throw new RunnerNotFoundError();
        }

        newScan.timestamp = Math.round(new Date().getTime() / 1000);
        newScan = await this.validateScan(newScan);

        return newScan;
    }

    /**
     * Get's a runnerCard entity via the provided id.
     * @returns The runnerCard whom's id you provided.
     */
    public async getCard(): Promise<RunnerCard> {
        const id = this.card % 200000000000;
        const runnerCard = await getConnection().getRepository(RunnerCard).findOne({ id: id }, { relations: ["runner"] });
        if (!runnerCard) {
            throw new RunnerCardNotFoundError();
        }
        return runnerCard;
    }

    /**
     * Get's a scanstation entity via the provided id.
     * @returns The scanstation whom's id you provided.
     */
    public async getStation(): Promise<ScanStation> {
        if (!this.station) {
            throw new BadRequestError("You are missing the station's id!")
        }
        const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station }, { relations: ["track"] });
        if (!station) {
            throw new ScanStationNotFoundError();
        }
        return station;
    }

    /**
     * Validates the scan and sets it's lap time;
     * @param scan The scan you want to validate
     * @returns The validated scan with it's laptime set.
     */
    public async validateScan(scan: TrackScan): Promise<TrackScan> {
        const latestScan = await getConnection().getRepository(TrackScan).findOne({ where: { runner: scan.runner, valid: true }, relations: ["track"], order: { id: 'DESC' } });
        if (!latestScan) {
            scan.lapTime = 0;
            scan.valid = true;
        }
        else {
            scan.lapTime = scan.timestamp - latestScan.timestamp;
            scan.valid = (scan.lapTime > scan.track.minimumLapTime);
        }
        return scan;
    }
}