// @ts-strict-ignore
import { Component, effect, inject, OnDestroy, OnInit, signal } from "@angular/core";
import { FormGroup, FormsModule, ReactiveFormsModule } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { ModalController, NavController } from "@ionic/angular";
import { FormlyFieldConfig, FormlyModule } from "@ngx-formly/core";
import { TranslateService } from "@ngx-translate/core";
import { NgxSpinnerComponent } from "ngx-spinner";
import { Subject } from "rxjs";
import { NavigationBackButtonComponent } from "src/app/shared/components/navigation/back-button/back-button";
import { JsonrpcRequest } from "src/app/shared/jsonrpc/base";
import { ComponentJsonApiRequest } from "src/app/shared/jsonrpc/request/componentJsonApiRequest";
import { PipeComponentsModule } from "src/app/shared/pipe/pipe.module";
import { RouteService } from "src/app/shared/service/route.service";
import { CommonUiModule } from "../../../shared/common-ui.module";
import { Edge, Service, Utils, Websocket } from "../../../shared/shared";
import { extractErrorMessage } from "../../../shared/utils/error/error.utils";
import { AddAppInstance } from "./jsonrpc/addAppInstance";
import { Flags } from "./jsonrpc/flag/flags";
import { GetApp } from "./jsonrpc/getApp";
import { GetAppAssistant } from "./jsonrpc/getAppAssistant";
import { AppCenter } from "./keypopup/appCenter";
import { AppCenterInstallAppWithSuppliedKeyRequest } from "./keypopup/appCenterInstallAppWithSuppliedKey";
import { AppCenterIsAppFree } from "./keypopup/appCenterIsAppFree";
import { KeyModalComponent, KeyValidationBehaviour } from "./keypopup/modal.component";
import { ConfigurationOAuthComponent } from "./steps/oauth/configuration-oauth.component";

@Component({
    selector: InstallAppComponent.SELECTOR,
    templateUrl: "./install.component.html",
    standalone: true,
    imports: [
        CommonUiModule,
        PipeComponentsModule,
        NgxSpinnerComponent,
        FormlyModule,
        FormsModule,
        ReactiveFormsModule,
        ConfigurationOAuthComponent,
        NavigationBackButtonComponent,
    ],
})
export class InstallAppComponent implements OnInit, OnDestroy {
    private static readonly SELECTOR = "app-install";

    public readonly spinnerId: string = InstallAppComponent.SELECTOR;

    protected form = signal<FormGroup | null>(null);
    protected fields: FormlyFieldConfig[] | null = null;
    protected model: any | null = null;
    protected appName: string | null = null;
    protected isInstalling: boolean = false;
    protected steps: GetAppAssistant.AppConfigurationStep[] = [];

    private stopOnDestroy: Subject<void> = new Subject<void>();
    private key: string | null = null;
    private useMasterKey: boolean = false;
    private appId: string | null = null;
    private edge: Edge | null = null;
    private isAppFree: boolean = false;
    private isAppFreeFromEdge: boolean = false;
    private routeService = inject(RouteService);
    private isRunning: boolean = false;

    public constructor(
        private route: ActivatedRoute,
        protected utils: Utils,
        private websocket: Websocket,
        private service: Service,
        private modalController: ModalController,
        private router: Router,
        private translate: TranslateService,
        private navController: NavController,
    ) {
        const queryParamsSignal = this.routeService.getQueryParams();
        effect(() => {
            const _queryParams = queryParamsSignal();
            if (this.isRunning) {
                return;
            }
            this.init();
        });
    }

    /**
     * Displays a error toast with the string supplied from the messageBuilder.
     *
     * @param service The service to open the toast with
     * @param messageBuilder The message supplier
     * @returns A method to handle a catch from a promise
     */
    public static errorToast(service: Service, messageBuilder: (reason) => string): (reason: any) => void {
        return (reason) => {
            const errorMessage = extractErrorMessage(reason);
            console.error(reason);
            service.toast(messageBuilder(errorMessage), "danger");
        };
    }

    ngOnInit() {
        this.init();
    }

    public init() {
        this.isRunning = true;
        this.service.startSpinner(this.spinnerId);
        const state = history?.state;
        if (state) {
            if ("appKey" in state) {
                this.key = state["appKey"];
            }
            if ("useMasterKey" in state) {
                this.useMasterKey = state["useMasterKey"];
            }
        }
        const appId = this.routeService.getQueryParam<string>("appId");
        const appName = this.routeService.getQueryParam<string>("name");
        this.appId = appId;
        this.service.setCurrentComponent(appName, this.route).then((edge) => {
            this.edge = edge;

            this.edge
                .sendRequest(
                    this.websocket,
                    new AppCenter.Request({
                        payload: new AppCenterIsAppFree.Request({
                            appId: this.appId,
                        }),
                    }),
                )
                .then((response) => {
                    const result = (response as AppCenterIsAppFree.Response).result;
                    this.isAppFree = result.isAppFree;
                })
                .catch(() => {
                    this.isAppFree = false;
                });

            this.edge
                .sendRequest(
                    this.websocket,
                    new ComponentJsonApiRequest({
                        componentId: "_appManager",
                        payload: new GetApp.Request({ appId: appId }),
                    }),
                )
                .then((response) => {
                    const result = (response as GetApp.Response).result;

                    this.isAppFreeFromEdge =
                        Flags.getByType(result.app.flags, Flags.FREE_FROM_DEPENDENCY) !== undefined;
                })
                .catch(
                    InstallAppComponent.errorToast(
                        this.service,
                        (error) => "Error while receiving App [" + appId + "]: " + error,
                    ),
                );
            edge.sendRequest(
                this.websocket,
                new ComponentJsonApiRequest({
                    componentId: "_appManager",
                    payload: new GetAppAssistant.Request({ appId: appId }),
                }),
            )
                .then((response) => {
                    const appAssistant = GetAppAssistant.postprocess((response as GetAppAssistant.Response).result);

                    this.fields = GetAppAssistant.getInitialFields(appAssistant.fields, {});
                    this.appName = appAssistant.name;
                    this.model = {};
                    this.form.set(new FormGroup({}));

                    // treat configuration as a installation step
                    this.steps = [
                        {
                            type: GetAppAssistant.AppConfigurationStepType.CONFIGURATION,
                            params: {},
                        },
                        ...(appAssistant.steps ?? []),
                    ];
                })
                .catch(
                    InstallAppComponent.errorToast(
                        this.service,
                        (error) => "Error while receiving App Assistant for [" + appId + "]: " + error,
                    ),
                )
                .finally(() => {
                    this.service.stopSpinner(this.spinnerId);
                    this.isRunning = false;
                });
        });
    }

    public ngOnDestroy(): void {
        this.stopOnDestroy.next();
        this.stopOnDestroy.complete();
    }

    /** Submit for installing a app. */
    protected submit() {
        this.obtainKey()
            .then((key) => {
                this.service.startSpinnerTransparentBackground(this.appId);
                // remove alias field from properties
                const alias = this.form().value["ALIAS"];
                const clonedFields = {};
                for (const item in this.form().value) {
                    if (item !== "ALIAS") {
                        clonedFields[item] = this.form().value[item];
                    }
                }

                let request: JsonrpcRequest = new ComponentJsonApiRequest({
                    componentId: "_appManager",
                    payload: new AddAppInstance.Request({
                        appId: this.appId,
                        alias: alias,
                        properties: clonedFields,
                        ...(key && { key: key }),
                    }),
                });
                // if key not set send request with supplied key
                if (!key && !this.isAppFreeFromEdge) {
                    request = new AppCenter.Request({
                        payload: new AppCenterInstallAppWithSuppliedKeyRequest.Request({
                            installRequest: request,
                        }),
                    });
                }

                this.isInstalling = true;
                this.edge
                    .sendRequest(this.websocket, request)
                    .then((response) => {
                        const result = (response as AddAppInstance.Response).result;

                        if (result.instance) {
                            result.instanceId = result.instance.instanceId;
                            this.model = result.instance.properties;
                        }
                        if (result.warnings && result.warnings.length > 0) {
                            this.service.toast(result.warnings.join(";"), "warning");
                        } else {
                            this.service.toast(this.translate.instant("EDGE.CONFIG.APP.SUCCESS_INSTALL"), "success");
                        }

                        this.form().markAsPristine();
                        if (this.routeService.getQueryParam("callback")) {
                            this.navController.back();
                            return;
                        }

                        if (this.steps.length > 1) {
                            this.router.navigate(["update"], {
                                queryParams: {
                                    name: this.appName,
                                    appId: this.appId,
                                },
                                relativeTo: this.route,
                            });
                        } else {
                            const navigationExtras = {
                                state: { appInstanceChange: true },
                            };
                            this.router.navigate(["../"], {
                                ...navigationExtras,
                                relativeTo: this.route,
                            });
                        }
                    })
                    .catch(
                        InstallAppComponent.errorToast(this.service, (error) =>
                            this.translate.instant("EDGE.CONFIG.APP.FAIL_INSTALL", { error: error }),
                        ),
                    )
                    .finally(() => {
                        this.isInstalling = false;
                        this.service.stopSpinner(this.appId);
                    });
            })
            .catch(() => {
                // can not get key => dont install
            });
    }

    /**
     * Gets the key to install the current app with.
     *
     * @returns The key or null if the predefined key gets used
     */
    private obtainKey(): Promise<string | null> {
        return new Promise<string | null>((resolve, reject) => {
            if (this.key) {
                resolve(this.key);
                return;
            }
            if (this.useMasterKey) {
                resolve(null);
                return;
            }
            if (this.isAppFree) {
                resolve(null);
                return;
            }
            if (this.isAppFreeFromEdge) {
                resolve(null);
                return;
            }
            this.presentModal().then(resolve).catch(reject);
        });
    }

    // popup for key
    private async presentModal(): Promise<string> {
        const modal = await this.modalController.create({
            component: KeyModalComponent,
            componentProps: {
                edge: this.edge,
                appId: this.appId,
                behaviour: KeyValidationBehaviour.SELECT,
                appName: this.appName,
                route: this.route,
            },
            cssClass: "auto-height",
        });

        const selectKeyPromise = new Promise<string>((resolve, reject) => {
            modal.onDidDismiss().then((event) => {
                if (!event.data) {
                    reject();
                    return; // no key selected
                }
                if (event.data?.useMasterKey) {
                    resolve(null);
                    return;
                }
                if (event.data?.key?.keyId) {
                    resolve(event.data.key.keyId);
                    return;
                }
                reject();
            });
        });

        await modal.present();
        return selectKeyPromise;
    }
}
