import * as React from 'react';
import '.././SettingsManager.scss';
import './ReportManager.scss';
import { action, observable } from 'mobx';
import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs';
import { CgClose } from 'react-icons/cg';
import { AiOutlineUpload } from 'react-icons/ai';
import { HiOutlineArrowLeft } from 'react-icons/hi';
import { Issue } from './reportManagerSchema';
import { observer } from 'mobx-react';
import { Doc } from '../../../fields/Doc';
import { Networking } from '../../Network';
import { MainViewModal } from '../../views/MainViewModal';
import { Octokit } from '@octokit/core';
import { Button, IconButton, OrientationType, Type } from 'browndash-components';
import Dropzone from 'react-dropzone';
import ReactLoading from 'react-loading';
import v4 = require('uuid/v4');
import { BugType, FileData, Priority, ViewState, inactiveBorderColor, inactiveColor } from './reportManagerUtils';
import { IssueCard, IssueView, Tag } from './ReportManagerComponents';
import { StrCast } from '../../../fields/Types';
const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;

// StrCast(Doc.UserDoc().userColor);
// StrCast(Doc.UserDoc().userBackgroundColor);
// StrCast(Doc.UserDoc().userVariantColor);

/**
 * Class for reporting and viewing Github issues within the app.
 */
@observer
export class ReportManager extends React.Component<{}> {
    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 bugTitle = '';
    @action setBugTitle = action((title: string) => {
        this.bugTitle = title;
    });
    @observable private bugDescription = '';
    @action setBugDescription = action((description: string) => {
        this.bugDescription = description;
    });
    @observable private bugType = '';
    @action setBugType = action((type: string) => {
        this.bugType = type;
    });
    @observable private bugPriority = '';
    @action setBugPriority = action((priortiy: string) => {
        this.bugPriority = priortiy;
    });

    @observable private mediaFiles: FileData[] = [];
    @action private setMediaFiles = (files: FileData[]) => {
        this.mediaFiles = files;
    };

    public close = action(() => (this.isOpen = false));
    public open = action(async () => {
        this.isOpen = true;
        if (this.shownIssues.length === 0) {
            this.setFetchingIssues(true);
            try {
                // load in the issues if not already loaded
                const issues = (await this.getAllIssues()) as Issue[];
                // filtering to include only open issues and exclude pull requests, maybe add a separate tab for pr's?
                this.setShownIssues(issues.filter(issue => issue.state === 'open' && !issue.pull_request));
            } catch (err) {
                console.log(err);
            }
            this.setFetchingIssues(false);
        }
    });

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

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

    /**
     * Fethches issues from Github.
     * @returns array of all issues
     */
    public async getAllIssues(): Promise<any[]> {
        const res = await this.octokit.request('GET /repos/{owner}/{repo}/issues', {
            owner: 'brown-dash',
            repo: 'Dash-Web',
            per_page: 80,
        });

        // 200 status means success
        if (res.status === 200) {
            return res.data;
        } else {
            throw new Error('Error getting issues');
        }
    }

    /**
     * Sends a request to Github to report a new issue with the form data.
     * @returns nothing
     */
    public async reportIssue(): Promise<void> {
        if (this.bugTitle === '' || this.bugDescription === '' || this.bugType === '' || this.bugPriority === '') {
            alert('Please fill out all required fields to report an issue.');
            return;
        }
        this.setSubmitting(true);

        const links = await this.uploadFilesToServer();
        console.log(links);
        if (!links) {
            // error uploading files to the server
            return;
        }
        const formattedLinks = (links ?? []).map(this.fileLinktoServerLink);

        // const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
        //     owner: 'brown-dash',
        //     repo: 'Dash-Web',
        //     title: this.formatTitle(this.bugTitle, Doc.CurrentUserEmail),
        //     body: `${this.bugDescription} ${formattedLinks.length > 0 && `\n\nFiles:\n${formattedLinks.join('\n')}`}`,
        //     labels: ['from-dash-app', this.bugType, this.bugPriority],
        // });

        // // 201 status means success
        // if (req.status !== 201) {
        //     alert('Error creating issue on github.');
        //     return;
        // }

        // Reset fields
        this.setBugTitle('');
        this.setBugDescription('');
        this.setMediaFiles([]);
        this.setBugType('');
        this.setBugPriority('');
        this.setSubmitting(false);
        this.setFetchingIssues(true);
        try {
            // load in the issues if not already loaded
            const issues = (await this.getAllIssues()) as Issue[];
            // filtering to include only open issues and exclude pull requests, maybe add a separate tab for pr's?
            this.setShownIssues(issues.filter(issue => issue.state === 'open' && !issue.pull_request));
        } catch (err) {
            console.log(err);
        }
        this.setFetchingIssues(false);
        alert('Successfully submitted issue.');
    }

    /**
     * Formats issue title.
     *
     * @param title title of issue
     * @param userEmail email of issue submitter
     * @returns formatted title
     */
    private formatTitle = (title: string, userEmail: string): string => `${title} - ${userEmail.replace('@brown.edu', '')}`;

    // turns an upload link -> server link
    // ex:
    // C: /Users/dash/Documents/GitHub/Dash-Web/src/server/public/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2.png
    // -> https://browndash.com/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2_l.png
    private fileLinktoServerLink = (fileLink: string) => {
        const serverUrl = 'https://browndash.com/';

        const regex = 'public';
        const publicIndex = fileLink.indexOf(regex) + regex.length;

        const finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1).replace('.', '_l.')}`;
        return finalUrl;
    };

    /**
     * Gets the server file path.
     *
     * @param link response from file upload
     * @returns server file path
     */
    private getServerPath = (link: any): string => {
        return link.result.accessPaths.agnostic.server as string;
    };

    /**
     * Uploads media files to the server.
     * @returns the server paths or undefined on error
     */
    private uploadFilesToServer = async (): Promise<string[] | undefined> => {
        try {
            // need to always upload to browndash
            const links = await Networking.UploadFilesToServer(
                this.mediaFiles.map(file => ({ file: file.file })),
                true
            );
            return (links ?? []).map(this.getServerPath);
        } catch (err) {
            if (err instanceof Error) {
                alert(err.message);
            } else {
                alert(err);
            }
        }
    };

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

    /**
     * Returns when the issue passes the current filters.
     *
     * @param issue issue to check
     * @returns boolean indicating whether the issue passes the current filters
     */
    private passesTagFilter = (issue: Issue) => {
        let passesPriority = true;
        let passesBug = true;
        if (this.priorityFilter) {
            passesPriority = issue.labels.some(label => {
                if (typeof label === 'string') {
                    return label === this.priorityFilter;
                } else {
                    return label.name === this.priorityFilter;
                }
            });
        }
        if (this.bugFilter) {
            passesBug = issue.labels.some(label => {
                if (typeof label === 'string') {
                    return label === this.bugFilter;
                } else {
                    return label.name === this.bugFilter;
                }
            });
        }
        return passesPriority && passesBug;
    };

    /**
     * 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.file;
        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.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} />
                    </div>
                </div>
            );
        } else 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.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} />
                    </div>
                </div>
            );
        } else 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.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} />
                    </div>
                </div>
            );
        }
        return <></>;
    };

    /**
     * @returns the component that dispays all issues
     */
    private viewIssuesComponent = () => {
        return (
            <div className="view-issues" style={{ backgroundColor: StrCast(Doc.UserDoc().userBackgroundColor) }}>
                <div className="left" style={{ display: this.rightExpanded ? 'none' : 'flex' }}>
                    <div className="report-header">
                        <h2 style={{ color: StrCast(Doc.UserDoc().userColor) }}>Open Issues</h2>
                        <Button
                            type={Type.TERT}
                            color={StrCast(Doc.UserDoc().userVariantColor)}
                            text="Report Issue"
                            onClick={() => {
                                this.setViewState(ViewState.CREATE);
                            }}
                        />
                    </div>
                    <input
                        className="report-input"
                        type="text"
                        placeholder="Filter by query..."
                        onChange={e => {
                            this.setQuery(e.target.value);
                        }}
                        required
                    />
                    <div className="issues-filters">
                        <div className="issues-filter">
                            <Tag
                                text={'All'}
                                onClick={() => {
                                    this.setPriorityFilter(null);
                                }}
                                fontSize="12px"
                                backgroundColor={this.priorityFilter === null ? StrCast(Doc.UserDoc().userVariantColor) : 'transparent'}
                                color={this.priorityFilter === null ? StrCast(Doc.UserDoc().userColor) : inactiveColor}
                                border
                                borderColor={this.priorityFilter === null ? StrCast(Doc.UserDoc().userVariantColor) : inactiveBorderColor}
                            />
                            {Object.values(Priority).map(p => {
                                return (
                                    <Tag
                                        key={p}
                                        text={p}
                                        onClick={() => {
                                            this.setPriorityFilter(p);
                                        }}
                                        fontSize="12px"
                                        backgroundColor={this.priorityFilter === p ? StrCast(Doc.UserDoc().userVariantColor) : 'transparent'}
                                        color={this.priorityFilter === p ? StrCast(Doc.UserDoc().userColor) : inactiveColor}
                                        border
                                        borderColor={this.priorityFilter === p ? StrCast(Doc.UserDoc().userVariantColor) : inactiveBorderColor}
                                    />
                                );
                            })}
                        </div>
                        <div className="issues-filter">
                            <Tag
                                text={'All'}
                                onClick={() => {
                                    this.setBugFilter(null);
                                }}
                                fontSize="12px"
                                backgroundColor={this.bugFilter === null ? StrCast(Doc.UserDoc().userVariantColor) : 'transparent'}
                                color={this.bugFilter === null ? StrCast(Doc.UserDoc().userColor) : inactiveColor}
                                border
                                borderColor={this.bugFilter === null ? StrCast(Doc.UserDoc().userVariantColor) : inactiveBorderColor}
                            />
                            {Object.values(BugType).map(b => {
                                return (
                                    <Tag
                                        key={b}
                                        text={b}
                                        onClick={() => {
                                            this.setBugFilter(b);
                                        }}
                                        fontSize="12px"
                                        backgroundColor={this.bugFilter === b ? StrCast(Doc.UserDoc().userVariantColor) : 'transparent'}
                                        color={this.bugFilter === b ? StrCast(Doc.UserDoc().userColor) : inactiveColor}
                                        border
                                        borderColor={this.bugFilter === b ? StrCast(Doc.UserDoc().userVariantColor) : inactiveBorderColor}
                                    />
                                );
                            })}
                        </div>
                    </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 => this.passesTagFilter(issue))
                                .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
                        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 tooltip="close" icon={<CgClose size="16px" />} onClick={this.close} />
                </div>
            </div>
        );
    };

    /**
     * @returns the form component for submitting issues
     */
    private reportIssueComponent = () => {
        return (
            <div className="report-issue">
                <div className="report-header-vertical">
                    <Button
                        type={Type.PRIM}
                        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>
                    <input className="report-input" value={this.bugTitle} type="text" placeholder="Title..." onChange={e => this.setBugTitle(e.target.value)} required />
                </div>
                <div className="report-section">
                    <label className="report-label">Please leave a description for the bug and how it can be recreated</label>
                    <textarea className="report-textarea" value={this.bugDescription} placeholder="Description..." onChange={e => this.setBugDescription(e.target.value)} required />
                </div>
                <div className="report-selects">
                    <select className="report-select" name="bugType" onChange={e => (this.bugType = e.target.value)}>
                        <option value="" disabled selected>
                            Type
                        </option>
                        <option className="report-opt" value={BugType.BUG}>
                            Bug
                        </option>
                        <option className="report-opt" value={BugType.COSMETIC}>
                            Poor Design or Cosmetic
                        </option>
                        <option className="report-opt" value={BugType.DOCUMENTATION}>
                            Poor Documentation
                        </option>
                        <option className="report-opt" value={BugType.ENHANCEMENT}>
                            New feature or request
                        </option>
                    </select>
                    <select className="report-select" name="priority" onChange={e => (this.bugPriority = e.target.value)}>
                        <option className="report-opt" value="" disabled selected>
                            Priority
                        </option>
                        <option value={Priority.LOW}>Low</option>
                        <option value={Priority.MEDIUM}>Medium</option>
                        <option value={Priority.HIGH}>High</option>
                    </select>
                </div>
                <Dropzone
                    onDrop={this.onDrop}
                    accept={{
                        'image/*': ['.png', '.jpg', '.jpeg', '.gif'],
                        'video/*': ['.mp4', '.mpeg', '.webm', '.mov'],
                        'audio/mpeg': ['.mp3'],
                        'audio/wav': ['.wav'],
                        'audio/ogg': ['.ogg'],
                    }}>
                    {({ getRootProps, getInputProps }) => (
                        <div {...getRootProps({ className: 'dropzone' })}>
                            <input {...getInputProps()} />
                            <div className="dropzone-instructions">
                                <AiOutlineUpload size={25} />
                                <p>Drop or select media that shows the bug (optional)</p>
                            </div>
                        </div>
                    )}
                </Dropzone>
                {this.mediaFiles.length > 0 && <ul className="file-list">{this.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}
                        onClick={() => {
                            this.reportIssue();
                        }}
                    />
                )}

                <div style={{ position: 'absolute', top: '4px', right: '4px' }}>
                    <IconButton 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();
        } else {
            return this.reportIssueComponent();
        }
    };

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