import { Octokit } from '@octokit/core';
import { Button, Dropdown, DropdownType, IconButton, Type } from '@dash/components';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { BsArrowsAngleContract, BsArrowsAngleExpand, BsX } from 'react-icons/bs';
import { CgClose } from 'react-icons/cg';
import { HiOutlineArrowLeft } from 'react-icons/hi';
import { MdRefresh } from 'react-icons/md';
import ReactLoading from 'react-loading';
import * as uuid from 'uuid';
import { ClientUtils } from '../../../ClientUtils';
import { Doc } from '../../../fields/Doc';
import { StrCast } from '../../../fields/Types';
import { MainViewModal } from '../../views/MainViewModal';
import '../SettingsManager.scss';
import { SettingsManager } from '../SettingsManager';
import './ReportManager.scss';
import { Filter, FormInput, FormTextArea, IssueCard, IssueView } from './ReportManagerComponents';
import { Issue } from './reportManagerSchema';
import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils';

/**
 * Class for reporting and viewing Github issues within the app.
 */
@observer
export class ReportManager extends React.Component<object> {
    // eslint-disable-next-line no-use-before-define
    public static Instance: ReportManager;
    @observable private isOpen = false;

    @observable private query = '';
    @action private setQuery = (q: string) => {
        this.query = q;
    };

    private octokit: Octokit;

    @observable viewState: ViewState = ViewState.VIEW;
    @action private setViewState = (state: ViewState) => {
        this.viewState = state;
    };
    @observable submitting: boolean = false;
    @action private setSubmitting = (submitting: boolean) => {
        this.submitting = submitting;
    };

    @observable fetchingIssues: boolean = false;
    @action private setFetchingIssues = (fetching: boolean) => {
        this.fetchingIssues = fetching;
    };

    @observable
    public shownIssues: Issue[] = [];
    @action setShownIssues = action((issues: Issue[]) => {
        this.shownIssues = issues;
    });

    @observable
    public priorityFilter: Priority | null = null;
    @action setPriorityFilter = action((priority: Priority | null) => {
        this.priorityFilter = priority;
    });

    @observable
    public bugFilter: BugType | null = null;
    @action setBugFilter = action((bug: BugType | null) => {
        this.bugFilter = bug;
    });

    @observable selectedIssue: Issue | undefined = undefined;
    @action setSelectedIssue = action((issue: Issue | undefined) => {
        this.selectedIssue = issue;
    });

    @observable rightExpanded: boolean = false;
    @action setRightExpanded = action((expanded: boolean) => {
        this.rightExpanded = expanded;
    });

    // Form state
    @observable private formData: ReportForm = emptyReportForm;
    @action setFormData = action((newData: ReportForm) => {
        this.formData = newData;
    });

    public close = action(() => {
        this.isOpen = false;
    });
    public open = action(async () => {
        this.isOpen = true;
        if (this.shownIssues.length === 0) {
            this.updateIssues();
        }
    });

    @action updateIssues = action(async () => {
        this.setFetchingIssues(true);
        try {
            const issues = (await getAllIssues(this.octokit)) as Issue[];
            this.setShownIssues(issues.filter(issue => issue.state === 'open' && !issue.pull_request));
        } catch (err) {
            console.log(err);
        }
        this.setFetchingIssues(false);
    });

    constructor(props: object) {
        super(props);
        makeObservable(this);
        ReportManager.Instance = this;

        // initializing Github connection
        this.octokit = new Octokit({
            auth: process.env.GITHUB_ACCESS_TOKEN,
        });
    }

    /**
     * Sends a request to Github to report a new issue with the form data.
     * @returns nothing
     */
    public async reportIssue(): Promise<void> {
        if (this.formData.title === '' || this.formData.description === '') {
            alert('Please fill out all required fields to report an issue.');
            return;
        }
        this.setSubmitting(true);
        let formattedLinks: string[] = [];
        if (this.formData.mediaFiles.length > 0) {
            const links = await uploadFilesToServer(this.formData.mediaFiles);
            if (links) {
                formattedLinks = links;
            }
        }

        const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
            owner: 'brown-dash',
            repo: 'Dash-Web',
            title: formatTitle(this.formData.title, ClientUtils.CurrentUserEmail()),
            body: `${this.formData.description} ${formattedLinks.length > 0 ? `\n\nFiles:\n${formattedLinks.join('\n')}` : ''}`,
            labels: ['from-dash-app', this.formData.type, this.formData.priority],
        });

        // 201 status means success
        if (req.status !== 201) {
            alert('Error creating issue on github.');
        } else {
            await this.updateIssues();
            alert('Successfully submitted issue.');
        }
        this.setFormData(emptyReportForm);
        this.setSubmitting(false);
    }

    /**
     * Handles file upload.
     *
     * @param files uploaded files
     */
    private onDrop = (files: File[]) => {
        this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...files.map(file => ({ _id: uuid.v4(), file }))] });
    };

    /**
     * Gets a JSX element to render a media preview
     * @param fileData file data
     * @returns JSX element of a piece of media (image, video, audio)
     */
    private getMediaPreview = (fileData: FileData): JSX.Element => {
        const { file } = fileData;
        const mimeType = file.type;
        const preview = URL.createObjectURL(file);

        if (mimeType.startsWith('image/')) {
            return (
                <div key={fileData._id} className="report-media-wrapper">
                    <div className="report-media-content">
                        <img height={100} alt={`Preview of ${file.name}`} src={preview} style={{ display: 'block' }} />
                    </div>
                    <div className="close-btn">
                        <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setFormData({ ...this.formData, mediaFiles: this.formData.mediaFiles.filter(f => f._id !== fileData._id) })} />
                    </div>
                </div>
            );
        }
        if (mimeType.startsWith('video/')) {
            return (
                <div key={fileData._id} className="report-media-wrapper">
                    <div className="report-media-content">
                        <video className="report-default-video" controls style={{ height: '100px', width: 'auto', display: 'block' }}>
                            <source src={preview} type="video/mp4" />
                            Your browser does not support the video tag.
                        </video>
                    </div>
                    <div className="close-btn">
                        <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setFormData({ ...this.formData, mediaFiles: this.formData.mediaFiles.filter(f => f._id !== fileData._id) })} />
                    </div>
                </div>
            );
        }
        if (mimeType.startsWith('audio/')) {
            return (
                <div key={fileData._id} className="report-audio-wrapper">
                    <audio src={preview} controls />
                    <div className="close-btn">
                        <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setFormData({ ...this.formData, mediaFiles: this.formData.mediaFiles.filter(f => f._id !== fileData._id) })} />
                    </div>
                </div>
            );
        }
        return <div />;
    };

    /**
     * @returns the component that dispays all issues
     */
    private viewIssuesComponent = () => {
        const darkMode = isDarkMode(SettingsManager.userBackgroundColor);
        const colors = darkMode ? darkColors : lightColors;

        return (
            <div className="view-issues" style={{ backgroundColor: SettingsManager.userBackgroundColor, color: colors.text }}>
                <div className="left" style={{ display: this.rightExpanded ? 'none' : 'flex' }}>
                    <div className="report-header">
                        <h2 style={{ color: colors.text }}>Open Issues</h2>
                        <div className="header-btns">
                            <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="refresh" icon={<MdRefresh size="16px" />} onClick={this.updateIssues} />
                            <Button
                                type={Type.TERT}
                                color={StrCast(Doc.UserDoc().userVariantColor)}
                                text="Report Issue"
                                onClick={() => {
                                    this.setViewState(ViewState.CREATE);
                                }}
                            />
                        </div>
                    </div>
                    <FormInput value={this.query} placeholder="Filter by query..." onChange={this.setQuery} />
                    <div className="issues-filters">
                        <Filter items={Object.values(Priority)} activeValue={this.priorityFilter} setActiveValue={p => this.setPriorityFilter(p)} />
                        <Filter items={Object.values(BugType)} activeValue={this.bugFilter} setActiveValue={b => this.setBugFilter(b)} />
                    </div>
                    <div className="issues">
                        {this.fetchingIssues ? (
                            <div style={{ flexGrow: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
                                <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userColor)} width={50} height={50} />
                            </div>
                        ) : (
                            this.shownIssues
                                .filter(issue => issue.title.toLowerCase().includes(this.query))
                                .filter(issue => passesTagFilter(issue, this.priorityFilter, this.bugFilter))
                                .map(issue => (
                                    <IssueCard
                                        key={issue.number}
                                        issue={issue}
                                        onSelect={() => {
                                            this.setSelectedIssue(issue);
                                        }}
                                    />
                                ))
                        )}
                    </div>
                </div>
                <div className="right">{this.selectedIssue ? <IssueView key={this.selectedIssue.number} issue={this.selectedIssue} /> : <div>No issue selected</div>} </div>
                <div style={{ position: 'absolute', top: '8px', right: '8px', display: 'flex', gap: '16px' }}>
                    <IconButton
                        color={StrCast(Doc.UserDoc().userColor)}
                        tooltip={this.rightExpanded ? 'Minimize right side' : 'Expand right side'}
                        icon={this.rightExpanded ? <BsArrowsAngleContract size="16px" /> : <BsArrowsAngleExpand size="16px" />}
                        onClick={e => {
                            e.stopPropagation();
                            this.setRightExpanded(!this.rightExpanded);
                        }}
                    />
                    <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={this.close} />
                </div>
            </div>
        );
    };

    /**
     * @returns the form component for submitting issues
     */
    private reportIssueComponent = () => {
        const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
        const colors = darkMode ? darkColors : lightColors;

        return (
            <div className="report-issue" style={{ color: colors.text }}>
                <div className="report-header-vertical">
                    <Button
                        type={Type.PRIM}
                        color={StrCast(Doc.UserDoc().userColor)}
                        text="back to view"
                        icon={<HiOutlineArrowLeft />}
                        iconPlacement="left"
                        onClick={() => {
                            this.setViewState(ViewState.VIEW);
                        }}
                    />
                    <h2>Report an Issue</h2>
                </div>
                <div className="report-section">
                    <label className="report-label">Please provide a title for the bug</label>
                    <FormInput value={this.formData.title} placeholder="Title..." onChange={val => this.setFormData({ ...this.formData, title: val })} />
                </div>
                <div className="report-section">
                    <label className="report-label">Please leave a description for the bug and how it can be recreated</label>
                    <FormTextArea value={this.formData.description} placeholder="Description..." onChange={val => this.setFormData({ ...this.formData, description: val })} />
                </div>
                <div className="report-selects">
                    <Dropdown
                        color={StrCast(Doc.UserDoc().userColor)}
                        formLabel="Type"
                        closeOnSelect
                        items={bugDropdownItems}
                        selectedVal={this.formData.type}
                        setSelectedVal={val => {
                            if (typeof val === 'string') this.setFormData({ ...this.formData, type: val as BugType });
                        }}
                        dropdownType={DropdownType.SELECT}
                        type={Type.TERT}
                        fillWidth
                    />
                    <Dropdown
                        color={StrCast(Doc.UserDoc().userColor)}
                        formLabel="Priority"
                        closeOnSelect
                        items={priorityDropdownItems}
                        selectedVal={this.formData.priority}
                        setSelectedVal={val => {
                            if (typeof val === 'string') this.setFormData({ ...this.formData, priority: val as Priority });
                        }}
                        dropdownType={DropdownType.SELECT}
                        type={Type.TERT}
                        fillWidth
                    />
                </div>
                <input
                    type="file"
                    accept="image/*, video/*, audio/*"
                    multiple
                    onChange={e => {
                        if (!e.target.files) return;
                        this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...Array.from(e.target.files).map(file => ({ _id: uuid.v4(), file }))] });
                    }}
                />
                {this.formData.mediaFiles.length > 0 && <ul className="file-list">{this.formData.mediaFiles.map(file => this.getMediaPreview(file))}</ul>}
                {this.submitting ? (
                    <Button
                        text="Submit"
                        type={Type.TERT}
                        color={StrCast(Doc.UserDoc().userVariantColor)}
                        icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
                        iconPlacement="right"
                        onClick={() => {
                            this.reportIssue();
                        }}
                    />
                ) : (
                    <Button
                        text="Submit"
                        type={Type.TERT}
                        color={StrCast(Doc.UserDoc().userVariantColor)}
                        onClick={() => {
                            this.reportIssue();
                        }}
                    />
                )}
                <div style={{ position: 'absolute', top: '4px', right: '4px' }}>
                    <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={this.close} />
                </div>
            </div>
        );
    };

    /**
     * @returns the component rendered to the modal
     */
    private reportComponent = () => {
        if (this.viewState === ViewState.VIEW) {
            return this.viewIssuesComponent();
        }
        return this.reportIssueComponent();
    };

    render() {
        return (
            <MainViewModal
                contents={this.reportComponent()}
                isDisplayed={this.isOpen}
                interactive
                closeOnExternalClick={this.close}
                dialogueBoxStyle={{ width: 'auto', minWidth: '300px', height: '85vh', maxHeight: '90vh', background: StrCast(Doc.UserDoc().userBackgroundColor), borderRadius: '8px' }}
            />
        );
    }
}
