import { JsonAstObject, JsonParseMode, parseJsonAst, } from '@angular-devkit/core';

import { SchematicContext, SchematicsException, Tree, } from '@angular-devkit/schematics';

import { pkgJson, } from './dependencies';

import { appendPropertyInAstObject, findPropertyInAstObject, insertPropertyInAstObjectInOrder, } from './json-utils';

import { get } from 'https';

export interface NodePackage {
    name: string;
    version: string;
}

export enum Configs {
    JsonIndentLevel = 4,
}

export const GIT_IGNORE_FILE = '.gitignore';

export function addPropertyToPackageJson(
    tree: Tree,
    context: SchematicContext,
    propertyName: string,
    propertyValue: { [key: string]: any }
) {
    const packageJsonAst = parseJsonAtPath(tree, pkgJson.Path);
    const pkgNode = findPropertyInAstObject(packageJsonAst, propertyName);
    const recorder = tree.beginUpdate(pkgJson.Path);

    if (!pkgNode) {
        // outer node missing, add key/value
        appendPropertyInAstObject(
            recorder,
            packageJsonAst,
            propertyName,
            propertyValue,
            Configs.JsonIndentLevel
        );
    } else if (pkgNode.kind === 'object') {
        // property exists, update values
        for (let [key, value] of Object.entries(propertyValue)) {
            const innerNode = findPropertyInAstObject(pkgNode, key);

            if (!innerNode) {
                // script not found, add it
                context.logger.debug(`creating ${key} with ${value}`);

                insertPropertyInAstObjectInOrder(
                    recorder,
                    pkgNode,
                    key,
                    value,
                    Configs.JsonIndentLevel
                );
            } else {
                // script found, overwrite value
                context.logger.debug(`overwriting ${key} with ${value}`);

                const {end, start} = innerNode;

                recorder.remove(start.offset, end.offset - start.offset);
                recorder.insertRight(start.offset, JSON.stringify(value));
            }
        }
    }

    tree.commitUpdate(recorder);
}

export function addPropertyToGitignore(tree: Tree, _context: SchematicContext, file: string) {
    if (tree.exists(GIT_IGNORE_FILE)) {
        const buffer = tree.read(GIT_IGNORE_FILE);
        if (buffer === null) {
            throw new SchematicsException(`Could not read .gitignore`);
        }

        const content = buffer.toString();
        _context.logger.debug('gitignore content' + content);

        const updatedContent = `${content}\n${file}`;

        tree.overwrite(GIT_IGNORE_FILE, updatedContent);
    } else {
        _context.logger.debug('no gitignore found');
    }
}

/**
 * Attempt to retrieve the latest package version from NPM
 * Return an optional "latest" version in case of error
 * @param packageName
 */
export function getLatestNodeVersion(packageName: string): Promise<NodePackage> {
    const DEFAULT_VERSION = 'latest';

    return new Promise((resolve) => {
        return get(`https://registry.npmjs.org/${packageName}`, (res) => {
            let rawData = '';
            res.on('data', (chunk) => (rawData += chunk));
            res.on('end', () => {
                try {
                    const response = JSON.parse(rawData);
                    const version = (response && response['dist-tags']) || {};

                    resolve(buildPackage(packageName, version.latest));
                } catch (e) {
                    resolve(buildPackage(packageName));
                }
            });
        }).on('error', () => resolve(buildPackage(packageName)));
    });

    function buildPackage(name: string, version: string = DEFAULT_VERSION): NodePackage {
        return {name, version};
    }
}

export function parseJsonAtPath(tree: Tree, path: string): JsonAstObject {
    const buffer = tree.read(path);

    if (buffer === null) {
        throw new SchematicsException('Could not read package.json.');
    }

    const content = buffer.toString();

    const json = parseJsonAst(content, JsonParseMode.Strict);
    if (json.kind != 'object') {
        throw new SchematicsException(
            'Invalid package.json. Was expecting an object'
        );
    }

    return json;
}