import {
    AbstractMesh,
    Color3,
    DynamicTexture,
    InstancedMesh,
    MeshBuilder,
    Scene,
    StandardMaterial,
    Vector3
} from "@babylonjs/core";
import {PouchdbPersistenceManager} from "../integration/pouchdbPersistenceManager";

export class NewRelicData {
    private key: string;
    private account: string;
    private data: any[];
    private readonly scene: Scene;
    private persistenceManager: PouchdbPersistenceManager;
    private policyLabels: AbstractMesh[] = [];

    constructor(persistenceManager: PouchdbPersistenceManager, scene: Scene) {
        this.persistenceManager = persistenceManager;
        this.scene = scene;
        this.persistenceManager.getNewRelicData()
            .then((data) => {
                this.data = data;
                this.getNewRelicData().then(() => {
                    this.drawGraph();
                });
            });
    }

    public setCredentials(key: string, account: string) {
        this.key = key;
        this.account = account;
    }

    public async clearData() {
        this.data = [];
        await this.persistenceManager.setNewRelicData(this.data);
    }

    public async getNewRelicData() {
        if (this.data && this.data.length > 0) {
            console.warn("Already have data, early return");
            return;
        }

        try {
            const res = await fetch('https://deepdiagram.com/.netlify/functions/nerdgraph', {
                method: 'POST',
                credentials: 'include',
                body: '{"query": "{actor { nrql(query: \\"select * from NrAiIncident  \\", accounts: ' + this.account + ') { results } } }"}',
                headers: {"Api-Key": this.key}
            });
            const data = await res.json();
            if (data?.data?.actor?.nrql?.results) {
                const newdata = data.data.actor.nrql.results.map((item: any) => {
                    item.id = item.incidentId;
                    item.policyName = item.policyName ? item.policyName : "No Policy";
                    return item
                });
                await this.persistenceManager.setNewRelicData(newdata);
                this.data = newdata;
            }

        } catch (err) {

        }

    }

    public drawGraph() {
        this.data.sort((a, b) => {
            return parseInt(a.openTime) - parseInt(b.openTime);
        });

        const duration = this.data[this.data.length - 1].openTime - this.data[0].openTime;
        const interval = 10 / duration;
        const first = parseInt(this.data[0].openTime);
        const material = new StandardMaterial("material", this.scene);
        material.diffuseColor = new Color3(0, 0, .7);
        material.alpha = .3;
        const baseMesh = MeshBuilder.CreateBox("baseItem", {width: 1, height: 1, depth: 1}, this.scene);
        baseMesh.material = material;

        const warningMaterial = new StandardMaterial("warningMaterial", this.scene);
        warningMaterial.diffuseColor = new Color3(.7, .7, .2);
        warningMaterial.alpha = .5;
        const warningMesh = MeshBuilder.CreateBox("warningItem", {width: 1, height: 1, depth: 1}, this.scene);
        warningMesh.material = warningMaterial;

        const criticalMaterial = new StandardMaterial("criticalMaterial", this.scene);
        criticalMaterial.diffuseColor = new Color3(.9, .2, .2);
        criticalMaterial.alpha = .7;
        const criticalMesh = MeshBuilder.CreateBox("criticalItem", {width: 1, height: 1, depth: 1}, this.scene);
        criticalMesh.material = criticalMaterial;


        const policies: Map<String, { x: number, y: number }> = new Map<string, { x: number, y: number }>();
        this.data.forEach((item) => {
            const policy = item.policyName ? item.policyName : "No Policy";
            let x;
            let y: number = 0;
            if (policies.has(policy)) {
                const value = policies.get(policy);
                x = value.x;
                y = value.y + .105;
                policies.set(policy, {x, y});
            } else {
                policies.set(policy, {x: policies.size / 10, y: 0});
                x = policies.get(policy).x;
                const policyLabel = this.buildText(policy);
                policyLabel.scaling = new Vector3(3, 3, 3);
                policyLabel.position = new Vector3(x, .4, 0);
                policyLabel.rotation.x = Math.PI / 2;
                policyLabel.rotation.y = -Math.PI / 2;

                this.policyLabels.push(policyLabel);
            }
            const start = parseInt(item.openTime) - first;
            let end = duration;
            if (item.closeTime) {
                end = parseInt(item.closeTime) - first;
            }
            let box: AbstractMesh;
            switch (item.priority) {
                case "critical":
                    box = new InstancedMesh(item.id, criticalMesh);
                    break;
                case "warning":
                    box = new InstancedMesh(item.id, warningMesh);
                    break;
                default:
                    box = new InstancedMesh(item.id, baseMesh);
            }

            box.position = new Vector3(x, y + .5, (start * interval));
            if (item.closeTime) {
                box.scaling = new Vector3(.1, .1, (end - start) * interval);
            } else {
                box.scaling = new Vector3(.1, .1, .01);
            }
            box.position.z = box.position.z + box.scaling.z / 2;

            const startLabel = this.buildText(new Date(start + first).toLocaleString());
            startLabel.position = box.position.add(new Vector3(.05, .05, 0));
            startLabel.position.z = (start * interval) - .01;

            const endLabel = this.buildText(new Date(end + first).toLocaleString());
            endLabel.position = box.position.add(new Vector3(.05, .05, 0));
            endLabel.position.z = (end * interval) + .01;

        });

        this.scene.onBeforeRenderObservable.add(() => {
            this.policyLabels.forEach((label) => {
                label.position.z = this.scene.activeCamera.globalPosition.z;
            });
        });
    }

    private buildText(text: string) {
        //Set font
        const height = 0.03;
        const font_size = 24;
        const font = "bold " + font_size + "px Arial";
        //Set height for dynamic texture
        const DTHeight = 1.5 * font_size; //or set as wished
        //Calc Ratio
        const ratio = height / DTHeight;

        //Use a temporary dynamic texture to calculate the length of the text on the dynamic texture canvas
        const temp = new DynamicTexture("DynamicTexture", 32, this.scene);
        const tmpctx = temp.getContext();
        tmpctx.font = font;
        const DTWidth = tmpctx.measureText(text).width + 8;

        //Calculate width the plane has to be
        const planeWidth = DTWidth * ratio;

        //Create dynamic texture and write the text
        const dynamicTexture = new DynamicTexture("DynamicTexture", {
            width: DTWidth,
            height: DTHeight
        }, this.scene, false);
        const mat = new StandardMaterial("mat", this.scene);
        mat.diffuseTexture = dynamicTexture;
        dynamicTexture.drawText(text, null, null, font, "#000000", "#ffffff", true);

        //Create plane and set dynamic texture as material
        const plane = MeshBuilder.CreatePlane("text", {width: planeWidth, height: height}, this.scene);

        plane.material = mat;

        return plane;
    }
}