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 { ScanStationHasScansError, ScanStationIdsNotMatchingError, ScanStationNotFoundError } from '../errors/ScanStationErrors';
import { TrackNotFoundError } from '../errors/TrackErrors';
import { deleteStationEntry } from '../nats/StationKV';
import { CreateScanStation } from '../models/actions/create/CreateScanStation';
import { UpdateScanStation } from '../models/actions/update/UpdateScanStation';
import { ScanStation } from '../models/entities/ScanStation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseScanStation } from '../models/responses/ResponseScanStation';
import { ScanController } from './ScanController';

@JsonController('/stations')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class ScanStationController {
	private stationRepository: Repository<ScanStation>;

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

	@Get()
	@Authorized("STATION:GET")
	@ResponseSchema(ResponseScanStation, { isArray: true })
	@OpenAPI({ description: 'Lists all stations. <br> This includes their associated tracks.' })
	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) {
		let responseStations: ResponseScanStation[] = new Array<ResponseScanStation>();
		let stations: Array<ScanStation>;

		if (page != undefined) {
			stations = await this.stationRepository.find({ relations: ['track'], skip: page * page_size, take: page_size });
		} else {
			stations = await this.stationRepository.find({ relations: ['track'] });
		}

		stations.forEach(station => {
			responseStations.push(station.toResponse());
		});
		return responseStations;
	}

	@Get('/:id')
	@Authorized("STATION:GET")
	@ResponseSchema(ResponseScanStation)
	@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
	@OnUndefined(ScanStationNotFoundError)
	@OpenAPI({ description: 'Lists all information about the station whose id got provided. <br> This includes it\'s associated track.' })
	async getOne(@Param('id') id: number) {
		let scan = await this.stationRepository.findOne({ id: id }, { relations: ['track'] })
		if (!scan) { throw new ScanStationNotFoundError(); }
		return scan.toResponse();
	}

	@Post()
	@Authorized("STATION:CREATE")
	@ResponseSchema(ResponseScanStation)
	@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
	@OpenAPI({ description: 'Create a new station. <br> Please remeber to provide the station\'s track\'s id. <br> Please also remember that the station key is only visibe on creation.' })
	async post(@Body({ validate: true }) createStation: CreateScanStation) {
		let newStation = await createStation.toEntity();
		const station = await this.stationRepository.save(newStation);
		let responseStation = (await this.stationRepository.findOne({ id: station.id }, { relations: ['track'] })).toResponse();
		responseStation.key = newStation.cleartextkey;
		return responseStation;
	}

	@Put('/:id')
	@Authorized("STATION:UPDATE")
	@ResponseSchema(ResponseScanStation)
	@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
	@ResponseSchema(ScanStationIdsNotMatchingError, { statusCode: 406 })
	@OpenAPI({ description: "Update the station whose id you provided. <br> Please remember that only the description and enabled state can be changed." })
	async put(@Param('id') id: number, @Body({ validate: true }) station: UpdateScanStation) {
		let oldStation = await this.stationRepository.findOne({ id: id });

		if (!oldStation) {
			throw new ScanStationNotFoundError();
		}

		if (oldStation.id != station.id) {
			throw new ScanStationIdsNotMatchingError();
		}

		await this.stationRepository.save(await station.update(oldStation));
		await deleteStationEntry(oldStation.prefix);
		return (await this.stationRepository.findOne({ id: id }, { relations: ['track'] })).toResponse();
	}

	@Delete('/:id')
	@Authorized("STATION:DELETE")
	@ResponseSchema(ResponseScanStation)
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
	@ResponseSchema(ScanStationHasScansError, { statusCode: 406 })
	@OnUndefined(204)
	@OpenAPI({ description: 'Delete the station whose id you provided. <br> If no station with this id exists it will just return 204(no content). <br> If the station still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with/created by this station - please disable it instead).' })
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
		let station = await this.stationRepository.findOne({ id: id });
		if (!station) { return null; }

		const stationScans = (await this.stationRepository.findOne({ id: station.id }, { relations: ["scans"] })).scans;
		if (stationScans.length != 0 && !force) {
			throw new ScanStationHasScansError();
		}
		const scanController = new ScanController;
		for (let scan of stationScans) {
			await scanController.remove(scan.id, force);
		}

		const responseStation = await this.stationRepository.findOne({ id: station.id }, { relations: ["track"] });
		await deleteStationEntry(station.prefix);
		await this.stationRepository.delete(station);
		return responseStation.toResponse();
	}
}
