import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm';
import { RunnerGroupNeededError, RunnerHasDistanceDonationsError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
import { deleteRunnerEntry } from '../nats/RunnerKV';
import { CreateRunner } from '../models/actions/create/CreateRunner';
import { UpdateRunner } from '../models/actions/update/UpdateRunner';
import { Runner } from '../models/entities/Runner';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunner } from '../models/responses/ResponseRunner';
import { ResponseScan } from '../models/responses/ResponseScan';
import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
import { DonationController } from './DonationController';
import { RunnerCardController } from './RunnerCardController';
import { ScanController } from './ScanController';

@JsonController('/runners')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerController {
	private runnerRepository: Repository<Runner>;

	/**
	 * Gets the repository of this controller's model/entity.
	 */
	constructor() {
		this.runnerRepository = getConnectionManager().get().getRepository(Runner);
	}

	@Get()
	@Authorized("RUNNER:GET")
	@ResponseSchema(ResponseRunner, { isArray: true })
	@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' })
	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100, @QueryParam("created_via", { required: false }) created_via: string = "all", @QueryParam("selfservice_links", { required: false }) selfservice_links: boolean = false) {
		let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
		let runners: Array<Runner>;

		console.log("call to RunnerController.getAll() with page: " + page + " and page_size: " + page_size + " and created_via: " + created_via + " and selfservice_links: " + selfservice_links);
		if (page != undefined) {
			runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'group.parentGroup', 'scans.track'], skip: page * page_size, take: page_size });
		} else {
			runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'group.parentGroup', 'scans.track'] });
		}

		runners.forEach(runner => {
			if (created_via === "all") {
				responseRunners.push(new ResponseRunner(runner, selfservice_links));
			} else {
				if (runner.created_via === created_via) {
					responseRunners.push(new ResponseRunner(runner, selfservice_links));
				}
			}
		});
		return responseRunners;
	}

	@Get('/:id')
	@Authorized("RUNNER:GET")
	@ResponseSchema(ResponseRunner)
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
	@OnUndefined(RunnerNotFoundError)
	@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
	async getOne(@Param('id') id: number) {
		let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations'] })
		if (!runner) { throw new RunnerNotFoundError(); }
		return new ResponseRunner(runner, true);
	}

	@Get('/:id/scans')
	@Authorized(["RUNNER:GET", "SCAN:GET"])
	@ResponseSchema(ResponseScan, { isArray: true })
	@ResponseSchema(ResponseTrackScan, { isArray: true })
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
	@OpenAPI({ description: 'Lists all scans of the runner whose id got provided. <br> If you only want the valid scans just add the ?onlyValid=true query param.' })
	async getScans(@Param('id') id: number, onlyValid?: boolean) {
		let responseScans: ResponseScan[] = new Array<ResponseScan>();
		let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'scans.track', 'scans.station', 'scans.runner'] })
		if (!runner) { throw new RunnerNotFoundError(); }

		if (!onlyValid) {
			for (let scan of runner.scans) {
				responseScans.push(scan.toResponse());
			}
		}
		else {
			for (let scan of runner.validScans) {
				responseScans.push(scan.toResponse());
			}
		}

		return responseScans;
	}

	@Post()
	@Authorized("RUNNER:CREATE")
	@ResponseSchema(ResponseRunner)
	@ResponseSchema(RunnerGroupNeededError)
	@ResponseSchema(RunnerGroupNotFoundError)
	@OpenAPI({ description: 'Create a new runner. <br> Please remeber to provide the runner\'s group\'s id.' })
	async post(@Body({ validate: true }) createRunner: CreateRunner) {
		let runner;
		try {
			runner = await createRunner.toEntity();
		} catch (error) {
			throw error;
		}

		runner = await this.runnerRepository.save(runner)
		return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }), true);
	}

	@Put('/:id')
	@Authorized("RUNNER:UPDATE")
	@ResponseSchema(ResponseRunner)
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
	@ResponseSchema(RunnerIdsNotMatchingError, { statusCode: 406 })
	@OpenAPI({ description: "Update the runner whose id you provided. <br> Please remember that ids can't be changed." })
	async put(@Param('id') id: number, @Body({ validate: true }) runner: UpdateRunner) {
		let oldRunner = await this.runnerRepository.findOne({ id: id }, { relations: ['group'] });

		if (!oldRunner) {
			throw new RunnerNotFoundError();
		}

		if (oldRunner.id != runner.id) {
			throw new RunnerIdsNotMatchingError();
		}

		await this.runnerRepository.save(await runner.update(oldRunner));
		await deleteRunnerEntry(id);
		return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }), true);
	}

	@Delete('/:id')
	@Authorized("RUNNER:DELETE")
	@ResponseSchema(ResponseRunner)
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
	@ResponseSchema(RunnerHasDistanceDonationsError, { statusCode: 406 })
	@OnUndefined(204)
	@OpenAPI({ description: 'Delete the runner whose id you provided. <br> This will also delete all scans and cards associated with the runner. <br> If no runner with this id exists it will just return 204(no content).' })
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
		let runner = await this.runnerRepository.findOne({ id: id });
		if (!runner) { return null; }
		const responseRunner = await this.runnerRepository.findOne(runner);

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

		const runnerDonations = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["distanceDonations"] })).distanceDonations;
		if (runnerDonations.length > 0 && !force) {
			throw new RunnerHasDistanceDonationsError();
		}
		const donationController = new DonationController();
		for (let donation of runnerDonations) {
			await donationController.remove(donation.id, force);
		}

		const runnerCards = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["cards"] })).cards;
		const cardController = new RunnerCardController;
		for (let card of runnerCards) {
			await cardController.remove(card.id, force);
		}

		const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans;
		const scanController = new ScanController;
		for (let scan of runnerScans) {
			await scanController.remove(scan.id, force);
		}

		await this.runnerRepository.delete(runner);
		return new ResponseRunner(responseRunner);
	}
}
