import { expect } from 'chai';
import { isRight } from 'fp-ts/lib/Either';
import nock from 'nock';

import { TargetState } from '~/src/types';
import * as legacy from '~/lib/legacy';
import * as config from '~/src/config';

describe('lib/legacy', () => {
	before(async () => {
		await config.initialized();

		// Set the device uuid and name
		// these migration methods read some data from the database
		// (and other data from the API)
		// which is also why they need to be defined as integration tests
		// TODO: when the supervisor is a full app, we'll be able to control updates
		// using contracts, meaning this legacy code can dissapear
		await config.set({ uuid: 'local' });
		await config.set({ name: 'my-device' });
	});

	describe('Converting target state v2 to v3', () => {
		it('accepts a local target state with empty configuration', async () => {
			const target = await legacy.fromV2TargetState({} as any, true);

			const decoded = TargetState.decode(target);
			if (!isRight(decoded)) {
				// We do it this way let the type guard be triggered
				expect.fail('Resulting target state is a valid v3 target state');
			}

			const decodedTarget = decoded.right;
			expect(decodedTarget)
				.to.have.property('local')
				.that.has.property('name')
				.that.equals('my-device');
			expect(decodedTarget)
				.to.have.property('local')
				.that.has.property('apps')
				.that.deep.equals({});
			expect(decodedTarget)
				.to.have.property('local')
				.that.has.property('config')
				.that.deep.equals({});
		});

		it('accepts a local target state for an app without releases', async () => {
			const target = await legacy.fromV2TargetState(
				{
					local: {
						name: 'my-new-name',
						config: {
							BALENA_SUPERVISOR_PORT: '11111',
						},
						apps: {
							'1': {
								name: 'hello-world',
							},
						},
					},
				} as any,
				true,
			);

			const decoded = TargetState.decode(target);
			if (!isRight(decoded)) {
				console.log(decoded.left);
				// We do it this way let the type guard be triggered
				expect.fail('Resulting target state is a valid v3 target state');
			}

			const decodedTarget = decoded.right;

			expect(decodedTarget)
				.to.have.property('local')
				.that.has.property('config')
				.that.has.property('BALENA_SUPERVISOR_PORT')
				.that.equals('11111');
			expect(decodedTarget)
				.to.have.property('local')
				.that.has.property('name')
				.that.equals('my-new-name');
			expect(decodedTarget).to.have.property('local').that.has.property('apps');

			const apps = decodedTarget.local.apps;

			expect(apps)
				.to.have.property('1')
				.that.has.property('name')
				.that.equals('hello-world');
			expect(apps)
				.to.have.property('1')
				.that.has.property('releases')
				.that.deep.equals({});
		});

		it('accepts a local target state with valid config and apps', async () => {
			const target = await legacy.fromV2TargetState(
				{
					local: {
						name: 'my-new-name',
						config: {
							BALENA_SUPERVISOR_PORT: '11111',
						},
						apps: {
							'1': {
								releaseId: 1,
								commit: 'localrelease',
								name: 'hello-world',
								services: {
									'1': {
										imageId: 1,
										serviceName: 'hello',
										image: 'ubuntu:latest',
										running: true,
										environment: {},
										labels: { 'io.balena.features.api': 'true' },
										privileged: true,
										ports: ['3001:3001'],
									},
								},
								networks: {
									my_net: {
										labels: {
											'io.balena.some.label': 'foo',
										},
									},
								},
								volumes: {
									my_volume: {
										labels: {
											'io.balena.some.label': 'foo',
										},
									},
								},
							},
						},
					},
				},
				true,
			);

			const decoded = TargetState.decode(target);
			if (!isRight(decoded)) {
				// We do it this way let the type guard be triggered
				expect.fail('Resulting target state is a valid v3 target state');
			}

			const decodedTarget = decoded.right;

			expect(decodedTarget)
				.to.have.property('local')
				.that.has.property('config')
				.that.has.property('BALENA_SUPERVISOR_PORT')
				.that.equals('11111');
			expect(decodedTarget)
				.to.have.property('local')
				.that.has.property('name')
				.that.equals('my-new-name');
			expect(decodedTarget).to.have.property('local').that.has.property('apps');

			const apps = decodedTarget.local.apps;
			expect(apps)
				.to.have.property('1')
				.that.has.property('releases')
				.that.has.property('localrelease');
			expect(apps)
				.to.have.property('1')
				.that.has.property('name')
				.that.equals('hello-world');

			const release = apps['1'].releases.localrelease;
			expect(release).to.have.property('id').that.equals(1);
			expect(release).to.have.property('services').that.has.property('hello');

			const service = release.services.hello;
			expect(service).to.have.property('image').that.equals('ubuntu:latest');
			expect(service)
				.to.have.property('composition')
				.that.deep.equals({ privileged: true, ports: ['3001:3001'] });

			expect(release)
				.to.have.property('networks')
				.that.has.property('my_net')
				.that.has.property('labels')
				.that.deep.equals({ 'io.balena.some.label': 'foo' });
			expect(release)
				.to.have.property('volumes')
				.that.has.property('my_volume')
				.that.has.property('labels')
				.that.deep.equals({ 'io.balena.some.label': 'foo' });
		});

		it('accepts a cloud target state and requests app uuid from API', async () => {
			const apiEndpoint = await config.get('apiEndpoint');

			nock(apiEndpoint)
				.get('/v6/application(1)?$select=uuid')
				.reply(200, { d: [{ uuid: 'some-uuid' }] });

			const target = await legacy.fromV2TargetState(
				{
					local: {
						name: 'my-new-name',
						config: {
							BALENA_SUPERVISOR_PORT: '11111',
						},
						apps: {
							'1': {
								name: 'hello-world',
							},
						},
					},
				} as any,
				false, // local = false
			);

			const decoded = TargetState.decode(target);
			if (!isRight(decoded)) {
				// We do it this way let the type guard be triggered
				expect.fail('Resulting target state is a valid v3 target state');
			}

			const decodedTarget = decoded.right;

			expect(decodedTarget)
				.to.have.property('local')
				.that.has.property('config')
				.that.has.property('BALENA_SUPERVISOR_PORT')
				.that.equals('11111');
			expect(decodedTarget)
				.to.have.property('local')
				.that.has.property('name')
				.that.equals('my-new-name');
			expect(decodedTarget).to.have.property('local').that.has.property('apps');

			const apps = decodedTarget.local.apps;

			expect(apps)
				.to.have.property('some-uuid')
				.that.has.property('name')
				.that.equals('hello-world');
			expect(apps)
				.to.have.property('some-uuid')
				.that.has.property('id')
				.that.equals(1);
			expect(apps)
				.to.have.property('some-uuid')
				.that.has.property('releases')
				.that.deep.equals({});
		});
	});
});
