import { ColorState, SketchPicker } from 'react-color';
import { Id } from '../../fields/FieldSymbols';
import { BoolCast, Cast, StrCast } from '../../fields/Types';
import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
import { FontIconBox } from '../views/nodes/button/FontIconBox';
import { DragManager } from './DragManager';
import { GroupManager } from './GroupManager';
import { CheckBox } from '../views/search/CheckBox';
import { undoBatch } from './UndoManager';
import * as React from 'react';
import './SettingsManager.scss';
import './ReportManager.scss';
import { action, computed, observable, runInAction } from 'mobx';
import { BsX } from 'react-icons/bs';
import { BiX } from 'react-icons/bi';
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 } from '@mui/material';
import { Oval } from 'react-loader-spinner';
import Dropzone from 'react-dropzone';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import { theme } from '../theme';
import v4 = require('uuid/v4');
const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;

enum ViewState {
    VIEW,
    CREATE,
}

interface FileData {
    _id: string;
    file: File;
}

// Format reference: "https://browndash.com/files/images/upload_cb31bc0fda59c96ca14193ec494f80cf_o.jpg" />

/**
 * 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 selectedIssue: Issue | undefined = undefined;
    @action setSelectedIssue = action((issue: Issue | undefined) => {
        this.selectedIssue = issue;
    });

    // 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[];
                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: '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',
        });
    }

    /**
     * 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',
        });

        // 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();
        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);
        alert('Successfully submitted issue.');
        // this.close();
    }

    /**
     * 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 into a servable 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 {
            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 }))]);
    };

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

    /**
     * @returns the component that dispays all issues
     */
    private viewIssuesComponent = () => {
        return (
            <div className="view-issues">
                <div className="left">
                    <div className="report-header">
                        <h2>Open Issues</h2>
                        <Button
                            variant="contained"
                            onClick={() => {
                                this.setViewState(ViewState.CREATE);
                            }}>
                            report issue
                        </Button>
                    </div>
                    <input
                        className="report-input"
                        type="text"
                        placeholder="Filter..."
                        onChange={e => {
                            this.setQuery(e.target.value);
                        }}
                        required
                    />
                    <div className="issues">
                        {this.fetchingIssues ? (
                            <div style={{ flexGrow: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
                                <Oval height={50} width={50} color={theme.palette.primary.main} visible={true} ariaLabel="oval-loading" secondaryColor={theme.palette.primary.main + 'b8'} strokeWidth={3} strokeWidthSecondary={3} />
                            </div>
                        ) : (
                            this.shownIssues
                                .filter(issue => issue.title.toLowerCase().includes(this.query))
                                .map(issue => (
                                    <IssueCard
                                        key={issue.number}
                                        issue={issue}
                                        onSelect={() => {
                                            this.setSelectedIssue(issue);
                                        }}
                                    />
                                ))
                        )}
                    </div>
                </div>
                <div className="right">{this.selectedIssue ? <IssueView issue={this.selectedIssue} /> : <div>No issue selected</div>}</div>
                <IconButton sx={{ position: 'absolute', top: '4px', right: '4px' }} onClick={this.close}>
                    <BiX size="16px" />
                </IconButton>
            </div>
        );
    };

    /**
     * @returns the form component for submitting issues
     */
    private reportIssueComponent = () => {
        return (
            <div className="report-issue">
                <div className="report-header-vertical">
                    <Button
                        variant="text"
                        onClick={() => {
                            this.setViewState(ViewState.VIEW);
                        }}
                        sx={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                        <HiOutlineArrowLeft color={theme.palette.primary.main} />
                        back to view
                    </Button>
                    <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="priority-low">
                            Bug
                        </option>
                        <option className="report-opt" value="priority-medium">
                            Poor Design or Cosmetic
                        </option>
                        <option className="report-opt" value="priority-high">
                            Poor Documentation
                        </option>
                    </select>
                    <select className="report-select" name="bigPriority" 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>}
                <Button
                    variant="contained"
                    sx={{ fontSize: '14px', display: 'flex', alignItems: 'center', gap: '1rem' }}
                    onClick={() => {
                        this.reportIssue();
                    }}>
                    Submit
                    {this.submitting && <Oval height={20} width={20} color="#ffffff" visible={true} ariaLabel="oval-loading" secondaryColor="#ffffff87" strokeWidth={3} strokeWidthSecondary={3} />}
                </Button>
                <IconButton sx={{ position: 'absolute', top: '4px', right: '4px' }} onClick={this.close}>
                    <BiX size={'16px'} />
                </IconButton>
            </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: '#ffffff', borderRadius: '8px' }}
            />
        );
    }
}

// Mini components to render issues

interface IssueCardProps {
    issue: Issue;
    onSelect: () => void;
}
const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
    return (
        <div className="issue" onClick={onSelect}>
            <label className="issue-label">#{issue.number}</label>
            <h3 className="issue-title">{issue.title}</h3>
        </div>
    );
};

interface IssueViewProps {
    issue: Issue;
}

const IssueView = ({ issue }: IssueViewProps) => {
    const parseBody = (body: string) => {
        const imgTagRegex = /<img\b[^>]*\/?>/;
        const fileRegex = /https:\/\/browndash\.com\/files/;
        const parts = body.split('\n');

        const modifiedParts = parts.map(part => {
            if (imgTagRegex.test(part)) {
                return getLinkFromTag(part);
            } else if (fileRegex.test(part)) {
                return getTagFromUrl(part);
            } else {
                return part;
            }
        });
        return modifiedParts.join('\n');
    };

    const getLinkFromTag = (tag: string) => {
        const regex = /src="([^"]+)"/;
        let url = '';
        const match = tag.match(regex);
        if (match) {
            url = match[1];
        }

        return `\n${url}`;
    };

    const getTagFromUrl = (url: string) => {
        const imgRegex = /https:\/\/browndash\.com\/files[/\\]images/;
        const videoRegex = /https:\/\/browndash\.com\/files[/\\]videos/;
        const audioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;

        if (imgRegex.test(url)) {
            return `${url}\n<img width="100%" alt="Issue asset" src=${url} />`;
        } else if (videoRegex.test(url)) {
            return url;
        } else if (audioRegex.test(url)) {
            return `${url}\n<audio src=${url} controls />`;
        }
    };

    return (
        <div className="issue-view">
            <span className="issue-label">
                Issue{' '}
                <a className="issue-link" href={issue.html_url} target="_blank">
                    #{issue.number}
                </a>
            </span>
            <h2 className="issue-title">{issue.title}</h2>
            <ReactMarkdown children={issue.body ? parseBody(issue.body as string) : ''} className="issue-content" linkTarget={'_blank'} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
        </div>
    );
};
