import * as fs from "fs";
import * as path from "path";
import {$log} from "ts-log-debug";
import {RestClient} from "typed-rest-client/RestClient";
import {Constants} from "../constants";
import {HttpClient} from "typed-rest-client/HttpClient";
import klawSync = require("klaw-sync");
import { lookup } from 'mime-types';
import {dirname, join, resolve} from "path";
import {ensureDir} from "fs-extra";
import {Version} from "./models/version";
import {isSourceAsset} from "./file-utils";
import {AssetParams} from "./models/asset-params";
import Auth from "./auth";
import {AssetFile} from "./models/asset-file";

export interface AssetFileRef {
    id: string;
    url: string;
}

export interface FileCollectionMap {
    [file: string]: AssetFileRef;
}

const routes = {
    version: (params: AssetVersion) => `${Constants.ASSET_ROOT_URL}/${params.name}/${params.version}`,
    versions: (params: AssetParams) => `${Constants.ASSET_ROOT_URL}/${params.user}/${params.collection}/versions`,
    assetfile: (params: AssetFile) => `${Constants.ASSETFILE_ROOT_URL}/${params.key}`
};

export default class Remote {

    auth = new Auth();

    constructor() {
    }

    async getRestClient(): Promise<RestClient> {
        const token = await this.auth.getToken();
        const modelNestClient = new RestClient('model-nest-cli', Constants.HOST, [], {
            headers: {Authorization: `Bearer ${token}`}
        });
        return modelNestClient;
    }

    async getDownloadClient(): Promise<HttpClient> {
        const token = await this.auth.getToken();
        const modelNestClient = new HttpClient('model-nest-cli', [], {
            headers: {Authorization: `Bearer ${token}`}
        });
        return modelNestClient;
    }


    async upload(assetName: string, localPath: string) {
        const client = await this.getRestClient();

        console.log('here');
        if (!fs.existsSync(localPath)) {
            $log.error(`${localPath} does not exist`);
            return;
        }

        let files = [path.relative(path.dirname(localPath), localPath)];
        if (fs.lstatSync(localPath).isDirectory()) {
            const fileStats = klawSync(localPath);
            for(const file of fileStats) {
                if (isSourceAsset(file.path)) {
                    if (dirname(file.path) != resolve(localPath)) {
                        throw Error("Root asset file (ie. fbx or obj), must be in the root of the uploaded directory");
                    }
                }
            }
            files = fileStats.map(f=> path.relative(localPath, f.path));
        }
        $log.debug(files);

        $log.debug(Constants.HOST);
        const uploadClient: RestClient = new RestClient('model-nest-cli');
        try {
            const url = `asset/${encodeURIComponent(assetName)}/upload`;
            $log.debug(url);
            $log.debug(JSON.stringify({files}, null, 2));
            const uploadUrlsResp = await client.create<FileCollectionMap>(url, {files});
            $log.debug(uploadUrlsResp);
            const uploadUrls = uploadUrlsResp.result;
            if (uploadUrls != null) {
                for(const filePath of Object.keys(uploadUrls)) {
                    const absPath = path.join(localPath, filePath);
                    if (fs.lstatSync(absPath).isFile()) {
                        const uploadResult = await uploadClient.uploadStream('PUT', uploadUrls[filePath].url, fs.createReadStream(absPath), {
                            additionalHeaders: {
                                'content-type': lookup(absPath) || 'application/octet-stream'
                            }
                        });
                        $log.debug(uploadResult);
                    }
                }
            }

        } catch (err) {
            $log.error(`Unexpected error:\n ${JSON.stringify(err, null, 2)}`);
            return;
        }
    }

    async getAssetVersion(packageVersion: AssetVersion): Promise<Version|null> {
        const client = await this.getRestClient();
        const versionEndpoint = routes.version(packageVersion);
        const resp = await client.get<Version>(versionEndpoint);
        return resp.result;
    }

    async downloadAsset(workspaceDir: string, packageVersion: AssetVersion, version: Version) {
        const client = await this.getDownloadClient();
        const packageDir = join(workspaceDir, packageVersion.name);
        await ensureDir(packageDir);
        const format = version.formats.find(f => f.derived == false);
        if (format) {
            for(const asset of format.resources) {
                const downloadPath = routes.assetfile(asset);
                const localPath = join(packageDir, asset.sourcePath);
                $log.debug(`Downloading to ${localPath}`);
                await ensureDir(dirname(localPath));
                let file: NodeJS.WritableStream = fs.createWriteStream(localPath);
                (await client.get(downloadPath)).message.pipe(file);
            }
        }
    }

}