/**
 * Copyright (c) 2022, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
 *
 * This software is the property of WSO2 LLC. and its suppliers, if any.
 * Dissemination of any information or reproduction of any material contained
 * herein in any form is strictly forbidden, unless permitted by WSO2 expressly.
 * You may not alter or remove any copyright or other notice from copies of this content.
 */

import { IdentifiableComponentInterface } from "@wso2is/core/models";
import { EmptyPlaceholder, LinkButton, PageLayout } from "@wso2is/react-components";
import { getEmptyPlaceholderIllustrations } from "apps/console/src/features/core";
import React, {
    FunctionComponent,
    MutableRefObject,
    ReactElement,
    UIEventHandler,
    useEffect,
    useRef,
    useState
} from "react";
import { useTranslation } from "react-i18next";
import { Icon, Label } from "semantic-ui-react";
import InfiniteScrollContainer from "../components/infinite-scroll-container";
import TimeRangeSelector from "../components/time-range-selector";
import { LogsConstants } from "../constants";
import useFetch from "../hooks/use-fetch";
import { InterfaceDiagnosticLogsRequest, InterfaceLogsFilter } from "../models/diagnostic-log-models";
import { getDateTimeWithOffset, getTimeFromTimestamp } from "../utils";

/**
 * Proptypes for the logs page component.
 */
type LogsPageInterface = IdentifiableComponentInterface;

/**
 * Number of logs fetched for a request.
 */
const LOG_FETCH_COUNT: number = LogsConstants.LOG_FETCH_COUNT;

/**
 * Height of a single log row in collapsed state.
 */
const LOG_ROW_HEIGHT: number = LogsConstants.LOG_ROW_HEIGHT;

const TIMERANGE_DROPDOWN_OPTIONS: Array<{ key: number; text: string; value: number; }> =
    LogsConstants.TIMERANGE_DROPDOWN_OPTIONS;

/**
 * Logs monitoring page.
 *
 * @param props - {@link LogsPageInterface} Props injected to the component.
 *
 * @returns Logs Page {@link React.ReactElement}
 */
const LogsPage: FunctionComponent<LogsPageInterface> = (
    props: LogsPageInterface
): ReactElement => {
    const { ["data-componentid"]: componentId } = props;

    const [ isPreviousEmpty, setIsPreviousEmpty ] = useState<boolean>(false);
    const [ isNextEmpty, setIsNextEmpty ] = useState<boolean>(false);
    const [ searchQuery, setSearchQuery ] = useState<string>("");
    const [ filterQuery, setFilterQuery ] = useState<string>("");
    const [ inputQuery, setInputQuery ] = useState<string>("");
    const [ endTime, setEndTime ] = useState<string>("");
    const [ startTime, setStartTime ] = useState<string>("");
    const [ timeZone ] = useState<string>("GMT+0000 UTC");
    const [ timeRange, setTimeRange ] = useState<number>(0.25);
    const [ filterMap, setFilterMap ] = useState<Map<string, string>>(new Map());
    const [ filterList, setFilterList ] = useState([]);
    const [ requestPayload, setRequestPayload ] = useState<InterfaceDiagnosticLogsRequest | null>(null);
    const [ showRefreshButton, setShowRefreshButton ] = useState<boolean>(false);

    const { t } = useTranslation();

    useEffect(() => {
        const current: number = getDateTimeWithOffset(timeZone);

        setEndTime(current.toString());
        setStartTime((current - 3600*1000*timeRange).toString());
    }, []);

    useEffect(() => {
        setShowRefreshButton(false);

        // If the time range is user defined
        if (timeRange === -1) {
            return;
        }

        // Show refresh button 2 minutes after data fetch
        setTimeout(() => setShowRefreshButton(true), 120000);
    }, [ endTime ]);

    useEffect(() => {
        let tempQuery: string = "";

        if (searchQuery) {
            if (filterQuery) {
                tempQuery = ` and ${searchQuery}`;
            } else {
                tempQuery = searchQuery;
            }
        }

        setRequestPayload({
            endTime: endTime,
            filter: `${filterQuery}${tempQuery}`,
            limit: LOG_FETCH_COUNT,
            startTime: startTime
        });
    }, [ endTime, startTime, timeZone ]);

    useEffect(() => {
        let tempQuery: string = "";

        if (searchQuery) {
            if (filterQuery) {
                tempQuery = ` and ${searchQuery}`;
            } else {
                tempQuery = searchQuery;
            }
        }
        setRequestPayload({
            endTime: endTime,
            filter: `${filterQuery}${tempQuery}`,
            limit: LOG_FETCH_COUNT,
            startTime: startTime
        });
    }, [ filterQuery ]);

    const scrollRef: MutableRefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
    const addFilterButtonRef: MutableRefObject<HTMLDivElement> = useRef();
    const { error, list, loading, next, previous } = useFetch(requestPayload);

    /**
     * Handles the behaviour of the infinite scroller
     */
    const handleScroll: UIEventHandler<HTMLDivElement> = (e: React.UIEvent<HTMLElement>) => {
        const element: any = e.target;

        /**
         * When the at the top of the log container
         */
        if (element.scrollTop === 0) {

            if (previous) {
                setRequestPayload({
                    filter: searchQuery,
                    limit: LOG_FETCH_COUNT,
                    previousToken: previous
                });
                setIsPreviousEmpty(false);
                setIsNextEmpty(false);
            } else {
                setIsPreviousEmpty(true);
            }
        }
        /**
         * When the at the bottom of the log container
         */
        if (element.scrollHeight - element.scrollTop === element.clientHeight) {
            if (next) {
                setRequestPayload({
                    filter: searchQuery,
                    limit: LOG_FETCH_COUNT,
                    nextToken: next
                });
                setIsNextEmpty(false);
                setIsPreviousEmpty(false);
            } else {
                setIsNextEmpty(true);
            }
        }
    };

    /**
     * Build filter query
     */
    const buildFilterQuery = () => {
        let tempQuery: string = "";

        filterMap.forEach((val: string) => {
            if (tempQuery) {
                tempQuery = `${tempQuery} and logentry co '${val}'`;
            } else {
                tempQuery = `logentry co '${val}'`;
            }
        });
        setFilterQuery(tempQuery);
    };

    /**
     * Search logs
     *
     * @param query - search query with filters
     */
    const handleSearch = (query: string) => {
        setSearchQuery(query);
        if (filterQuery) {
            query = ` and ${query}`;
        }
        setRequestPayload({
            endTime: endTime,
            filter: `${filterQuery}${query}`,
            limit: LOG_FETCH_COUNT,
            startTime: startTime
        });
    };

    /**
     * Handles the `onSearchQueryClear` callback action.
     */
    const handleSearchQueryClear = (): void => {
        setSearchQuery("");
        setInputQuery("");
        setRequestPayload({
            endTime: endTime,
            filter: filterQuery,
            limit: LOG_FETCH_COUNT,
            startTime: startTime
        });
    };

    /**
     * Handles the search query submit.
     *
     * @param event - HTML Form Event
     */
    const handleSearchQuerySubmit = (event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault();

        const query: string = event.currentTarget.elements["query"].value;

        if (!query) {
            return;
        }
        handleSearch(`logentry co '${query}'`);
    };

    /**
     * Handles searching logs with multiple filters
     */
    const handleFilter = (query: InterfaceLogsFilter): void => {
        const tempMap: Map<string, string> = filterMap;

        tempMap.set(query.property, query.value);
        setFilterMap(tempMap);
        buildFilterQuery();

        const arr: { key: string, value:string }[] = [];

        tempMap.forEach((value: string, key: string) => {
            arr.push({ key, value });
        });

        setFilterList(arr);
    };

    /**
     * Remove specific filter attributes on click delete button
     * in filter pills
     */
    const removeFilter = (filterKey: string): void => {
        const tempMap: Map<string, string> = filterMap;

        tempMap.delete(filterKey);
        setFilterMap(tempMap);
        buildFilterQuery();

        const arr: { key: string, value:string }[] = [];

        tempMap.forEach((value: string, key: string) => {
            arr.push({ key, value });
        });

        setFilterList(arr);
    };

    /**
     * Clear all filter attributes
     */

    const clearAllFilters = (): void => {
        const tempMap: Map<string, string> = filterMap;

        tempMap.clear();
        setFilterMap(tempMap);
        buildFilterQuery();
        setFilterList([]);
    };

    /**
     * Returns search component
     */
    const advancedSearchFilter = (): ReactElement => (
        <form className="advance-search-form" onSubmit={ handleSearchQuerySubmit }>
            <div className="search-input-wrapper">
                <div className="search-box-addons">
                    <Icon name="search" color="grey" />
                </div>
                <div className="search-box">
                    <input
                        autoComplete="off"
                        placeholder={ t("extensions:develop.monitor.filter.searchBar.placeholder") }
                        maxLength={ 120 }
                        name="query"
                        type="text"
                        className="search-input"
                        value={ inputQuery }
                        onChange={ (e: React.FormEvent<HTMLInputElement>) =>
                            setInputQuery((e.target as HTMLInputElement).value)
                        }
                        data-componentid={ `${componentId}-search-input` }
                    />
                </div>
                <div className="search-box-addons" hidden={ !inputQuery }>
                    <Icon
                        name="close"
                        color="grey"
                        onClick={ handleSearchQueryClear }
                        className="icon-button"
                    />
                </div>
                <input
                    hidden
                    type="submit"
                    value="Submit"
                    data-componentid={ `${componentId}-search-input-submit` }
                />
            </div>
        </form>
    );

    const resolveContent = (): ReactElement => {
        if (error) {
            return (
                <EmptyPlaceholder
                    subtitle={ [
                        t("extensions:develop.monitor.notifications.genericError.subtitle.0"),
                        t("extensions:develop.monitor.notifications.genericError.subtitle.1")
                    ] }
                    title={ t("extensions:develop.monitor.notifications.genericError.title") }
                    image={ getEmptyPlaceholderIllustrations().genericError }
                    imageSize="tiny"
                    data-componentid={ `${ componentId }-generic-error-placeholder` }
                />
            );
        } else if (searchQuery && list.length === 0) {
            return (
                <EmptyPlaceholder
                    action={ (
                        <LinkButton onClick={ handleSearchQueryClear }>
                            { t("extensions:develop.monitor.notifications.emptySearchResult.actionLabel") }
                        </LinkButton>
                    ) }
                    image={ getEmptyPlaceholderIllustrations().emptySearch }
                    imageSize="tiny"
                    title={ t("extensions:develop.monitor.notifications.emptySearchResult.title") }
                    subtitle={ [
                        t("extensions:develop.monitor.notifications.emptySearchResult.subtitle.0"),
                        t("extensions:develop.monitor.notifications.emptySearchResult.subtitle.1")
                    ] }
                    data-componentid={ `${ componentId }-empty-search-placeholder` }
                />
            );
        } else if (filterQuery && list.length === 0) {
            return (
                <EmptyPlaceholder
                    action={ (
                        <LinkButton onClick={ clearAllFilters }>
                            { t("extensions:develop.monitor.notifications.emptyFilterResult.actionLabel") }
                        </LinkButton>
                    ) }
                    image={ getEmptyPlaceholderIllustrations().emptySearch }
                    imageSize="tiny"
                    title={ t("extensions:develop.monitor.notifications.emptyFilterResult.title") }
                    subtitle={ [
                        t("extensions:develop.monitor.notifications.emptyFilterResult.subtitle.0"),
                        t("extensions:develop.monitor.notifications.emptyFilterResult.subtitle.1")
                    ] }
                    data-componentid={ `${ componentId }-empty-filter-placeholder` }
                />
            );
        } else if (list.length === 0 && (!next || !previous) && !loading) {
            return (
                <EmptyPlaceholder
                    image={ getEmptyPlaceholderIllustrations().emptySearch }
                    imageSize="tiny"
                    title={ t("extensions:develop.monitor.notifications.emptyResponse.title") }
                    subtitle={ [
                        t("extensions:develop.monitor.notifications.emptyResponse.subtitle.0") +
                        `${ TIMERANGE_DROPDOWN_OPTIONS.find(
                            (el: {key: number; text: string; value: number;}) =>
                                el.value === timeRange)?.text.toLowerCase()
                        }`,
                        t("extensions:develop.monitor.notifications.emptyResponse.subtitle.1")
                    ] }
                    data-componentid={ `${ componentId }-empty-data-placeholder` }
                />
            );
        } else {
            return (
                (<InfiniteScrollContainer
                    handleScroll={ handleScroll }
                    scrollRef={ scrollRef }
                    logs={ list }
                    loading={ loading }
                    rowHeight= { LOG_ROW_HEIGHT }
                    logCount={ LOG_FETCH_COUNT }
                    isPreviousEmpty = { isPreviousEmpty }
                    isNextEmpty = { isNextEmpty }
                    setSearchQuery = { handleFilter }
                    data-componentid={ componentId }
                />)
            );
        }
    };

    const resolveClearAllFilters = (): ReactElement => {
        if (filterQuery) {
            return (
                <LinkButton
                    basic
                    compact
                    primary={ false }
                    data-componentid={ `${ componentId }-clear-filter-button` }
                    onClick={ clearAllFilters }
                    disabled={ !filterQuery }
                >
                    {
                        t("extensions:develop.monitor.filter.topToolbar" +
                            ".buttons.clearFilters.label")
                    }
                </LinkButton>
            )
        }
    }

    const fetchLatestLogs = (): void => {
        const current: number = getDateTimeWithOffset(timeZone);

        setEndTime(current.toString());
        setStartTime((current - 3600*1000*timeRange).toString());
    };

    const renderRefreshButton = (): ReactElement => {
        const isoDateString: string = new Date(Number(endTime)).toISOString();

        return (
            <div className="logs-refresh-component">
                { t("extensions:develop.monitor.filter.refreshButton.text") }
                { getTimeFromTimestamp(isoDateString) }
                <a onClick={ fetchLatestLogs }>
                    <Icon name="refresh" />
                    { t("extensions:develop.monitor.filter.refreshButton.linkText") }
                </a>
            </div>
        );
    };

    /**
     * This function is used to determine the label value for filter text. For now, the only difference is with traceId
     * Since BE haven't changed requestId to traceId yet. So we need to have this mapper function not to confuse user.
     * This needs to be removed when the BE done the needed migrations from requestId to traceId
     * Check https://github.com/wso2-enterprise/asgardeo-product/issues/14970 for more info.
     *
     * @param filterValue - Raw filter value to get the mapped label
     */
    const getLabelTextForFilterPill = (filterValue: string): string => {
        if (filterValue === "requestId") {
            return "traceId";
        }

        return filterValue;
    };

    return (
        <div className="diagnostic-logs">
            <PageLayout
                title={ t("extensions:develop.monitor.pageHeader.title") }
                pageTitle="Logs"
                description={
                    t("extensions:develop.monitor.pageHeader.description")
                }
                data-componentid={ `${componentId}-layout` }
            >
                <div>
                    <div className="top-action-bar">
                        { advancedSearchFilter() }
                        <TimeRangeSelector
                            setFromTime={ (value: string): void => setStartTime(value) }
                            setToTime={ (value: string): void => setEndTime(value) }
                            setTimeRange={ (value: number): void => setTimeRange(value) }
                            data-componentid={ componentId }
                        />
                    </div>
                    <div>
                        <>
                            <div className="top-toolbar">
                                {
                                    showRefreshButton ? renderRefreshButton() : null
                                }
                                <Label.Group>
                                    { filterList && filterList.map(
                                        (value: { key: string, value:string }, index: number) =>
                                            (<Label key={ index } className="filter-pill">
                                                { getLabelTextForFilterPill(value.key) }
                                                <Label.Detail>{ value.value }</Label.Detail>
                                                <Icon name="delete" onClick={ () => removeFilter(value.key) }></Icon>
                                            </Label>)
                                    ) }
                                    { resolveClearAllFilters() }
                                </Label.Group>
                            </div>
                            { resolveContent() }
                        </>
                    </div>
                </div>
            </PageLayout>
        </div>
    );
};

/**
 * Default props for the component.
 */
LogsPage.defaultProps = {
    "data-componentid": "logs-page"
};

export default LogsPage;
