import * as React from "react";
import { useState, createRef } from "react";
import { Link } from "react-router-dom";
import * as ReactGA from "react-ga";
import { Alert, Button, Card, Col, Modal, Row } from "react-bootstrap";
import Dialog from "react-bootstrap-dialog";

import {
    BluejeansMetadata, EnabledBackendName, Meeting, MeetingBackend, MeetingStatus, MyUser,
    QueueAttendee, User, VideoBackendNames, ZoomMetadata
} from "../models";
import {
    checkForbiddenError, Breadcrumbs, DateTimeDisplay, DisabledMessage, EditToggleField, ErrorDisplay,
    FormError, JoinedQueueAlert, LoadingDisplay, LoginDialog, showConfirmation, StatelessInputGroupForm
} from "./common";
import { DialInContent } from "./dialIn";
import { BackendSelector, getBackendByName } from "./meetingType";
import { PageProps } from "./page";
import { usePromise } from "../hooks/usePromise";
import * as api from "../services/api";
import { useQueueWebSocket, useUserWebSocket } from "../services/sockets";
import { addMeetingAutoAssigned, redirectToLogin } from "../utils";
import { meetingAgendaSchema } from "../validation";


interface JoinQueueProps {
    queue: QueueAttendee;
    backends: MeetingBackend[];
    onJoinQueue: (backend: string) => void;
    disabled: boolean;
    selectedBackend: string;
    onChangeSelectedBackend: (backend: string) => void;
}

const JoinQueue: React.FC<JoinQueueProps> = (props) => {
    return (
        <>
        <div className="row col-lg">
            <p className="mb-0">Select Meeting Type</p>
            <p className="mb-0 required">*</p>
        </div>
        <BackendSelector backends={props.backends} allowedBackends={new Set(props.queue.allowed_backends)}
            onChange={props.onChangeSelectedBackend} selectedBackend={props.selectedBackend}/>
        <div className="row">
            <div className="col-lg">
                <button disabled={props.disabled} onClick={() => props.onJoinQueue(props.selectedBackend)} type="button" className="btn btn-primary bottom-content">
                    Join Queue
                </button>
            </div>
        </div>
        </>
    );
}

interface QueueAttendingProps {
    queue: QueueAttendee;
    backends: MeetingBackend[];
    user: User;
    joinedQueue?: QueueAttendee | null;
    disabled: boolean;
    onJoinQueue: (backend: string) => void;
    onLeaveQueue: (myMeeting: Meeting) => void;
    onLeaveAndJoinQueue: (backend: string) => void;
    onChangeAgenda: (agenda: string) => void;
    onShowDialog: () => void;
    onChangeBackendType: (oldBackendType: string, backend: string) => void;
    selectedBackend: string;
    onChangeBackend: (backend: string) => void;
}

function QueueAttendingNotJoined(props: QueueAttendingProps) {
    const joinedOther = props.joinedQueue && props.joinedQueue.id !== props.queue.id;
    
    const notJoinedInpersonMeetingText = (
        props.queue.inperson_location === ''
            ? (
                'The host(s) have not specified an in-person meeting location.'
            )
            : <>In-person meetings will take place at: <strong>{props.queue.inperson_location}</strong></>
    );

    const controls = props.queue.status !== "closed" && (
        joinedOther && props.joinedQueue
            ? (
                <>
                <div className="row">
                    <div className="col-lg">
                        <JoinedQueueAlert joinedQueue={props.joinedQueue}/>
                    </div>
                </div>
                <JoinQueue queue={props.queue} backends={props.backends} onJoinQueue={props.onLeaveAndJoinQueue} disabled={props.disabled}
                    selectedBackend={props.selectedBackend} onChangeSelectedBackend={props.onChangeBackend}/>
                </>
            )
            : (
                <JoinQueue queue={props.queue} backends={props.backends} onJoinQueue={props.onJoinQueue} disabled={props.disabled}
                    selectedBackend={props.selectedBackend} onChangeSelectedBackend={props.onChangeBackend}/>
            )
    );
    const closedAlert = props.queue.status === "closed"
        && <Alert variant="dark"><strong>This queue is currently closed.</strong> Please return at a later time or message the queue host to find out when the queue will be open.</Alert>
    return (
        <>
        {closedAlert}
        <div className="row">
            <ul>
                <li>Number of people currently in line: <strong>{props.queue.line_length}</strong></li>
                <li>You are not in the meeting queue yet</li>
                {
                    props.queue.allowed_backends.includes('inperson') && 
                    <li>{notJoinedInpersonMeetingText}</li>
                }
            </ul>
        </div>
        {controls}
        </>
    );
}


interface TurnAlertProps {
    meetingType: EnabledBackendName;
}

const MeetingReadyAlert = (props: TurnAlertProps) => {
    const typeEnding = props.meetingType === 'inperson'
        ? 'go to the in-person meeting location to meet with the host'
        : 'follow the directions to join the meeting now';

    return <Alert variant="success">The host is ready for you! If you haven't already, {typeEnding}.</Alert>;
}

interface WaitingTurnAlertProps extends TurnAlertProps {
    placeInLine: number;
}

const WaitingTurnAlert = (props: WaitingTurnAlertProps) => {
    const inPersonEnding = '-- we will tell you when the host is ready. Make sure you are nearby the in-person meeting location.';
    const videoMessageEnding = 'so you can join the meeting once it is created.';

    const typeEnding = props.meetingType === 'inperson' ? inPersonEnding : videoMessageEnding;

    const placeBeginning = props.placeInLine > 0
        ? "It's not your turn yet, but the host may be ready for you at any time."
        : "You're up next, but the host isn't quite ready for you.";
    return (
        <Alert variant="warning">
            {placeBeginning} Pay attention to this page {typeEnding}
        </Alert>
    );
}

interface JoinedClosedAlertProps { meetingStatus: MeetingStatus }

const JoinedClosedAlert = (props: JoinedClosedAlertProps) => {
    const statusClause = props.meetingStatus === MeetingStatus.STARTED ? 'your meeting is still in progress' : 'you are still in line';
    return (
        <Alert variant="dark">
            This queue has been closed by the host, but {statusClause}.
            If you are unsure if your meeting will still happen, please contact the host.
        </Alert>
    );
}

interface VideoMeetingInfoProps {
    metadata: BluejeansMetadata | ZoomMetadata;
    backend: MeetingBackend;
}

const VideoMeetingInfo: React.FC<VideoMeetingInfoProps> = (props) => {
    const docLinkTag = (
        <a
            href={props.backend.docs_url === null ? undefined : props.backend.docs_url}
            target='_blank'
            className='card-link'
        >
            Getting Started with {props.backend.friendly_name} at U-M
        </a>
    );

    return (
        <>
        <Row>
            <Col md={6} sm={true}>
                <Card>
                    <Card.Body>
                        <Card.Title as='h5' className='mt-0'>Joining the Meeting</Card.Title>
                        <Card.Text>
                            Once the meeting is created, click Join Meeting to join the meeting and wait for the host.
                            Download the app and test your audio now.
                            Refer to {docLinkTag} for additional help getting started.
                        </Card.Text>
                    </Card.Body>
                </Card>
            </Col>
            <Col md={6} sm={true}>
                <Card>
                    <Card.Body>
                        <Card.Title className='mt-0'>Having Trouble with Video?</Card.Title>
                        <Card.Text><DialInContent {...props} /></Card.Text>
                    </Card.Body>
                </Card>
            </Col>
        </Row>
        </>
    );
}


function QueueAttendingJoined(props: QueueAttendingProps) {
    const meeting = props.queue.my_meeting!;
    const meetingBackend = getBackendByName(meeting.backend_type, props.backends);
    const isVideoMeeting = VideoBackendNames.includes(meetingBackend.name);
    const inProgress = meeting.status === MeetingStatus.STARTED;

    // Alerts and head
    const closedAlert = props.queue.status === 'closed' && <JoinedClosedAlert meetingStatus={meeting.status} />;

    const turnAlert = meeting.line_place !== null
        ? <WaitingTurnAlert meetingType={meetingBackend.name} placeInLine={meeting.line_place} />
        : <MeetingReadyAlert meetingType={meetingBackend.name} />;

    const headText = inProgress ? 'Your meeting is in progress.' : 'You are currently in line.';

    // Card content
    const changeMeetingType = props.queue.my_meeting?.assignee
        ? <small className="ml-2">(A Host has been assigned to this meeting. Meeting Type can no longer be changed.)</small>
        : <Button variant='link' onClick={props.onShowDialog} aria-label='Change Meeting Type' disabled={props.disabled}>Change</Button>;

    const notificationBlurb = !inProgress
        && (
            <Alert variant="info">
                <small>
                    Did you know? You can receive an SMS (text) message when
                    it's your turn by adding your cell phone number and
                    enabling attendee notifications in
                    your <Link to="/preferences">User Preferences</Link>.
                </small>
            </Alert>
        );

    const agendaBlock = !inProgress
        ? (
            <>
            <Card.Text><strong>Meeting Agenda</strong> (Optional):</Card.Text>
            <Card.Text><small>Let the host(s) know the topic you wish to discuss.</small></Card.Text>
            <EditToggleField
                id='agenda'
                value={meeting.agenda}
                formLabel='Meeting Agenda'
                placeholder=''
                buttonOptions={{ onSubmit: props.onChangeAgenda, buttonType: 'success' }}
                disabled={props.disabled}
                fieldComponent={StatelessInputGroupForm}
                fieldSchema={meetingAgendaSchema}
                showRemaining={true}
                initialState={!meeting.agenda}
            >
                Update
            </EditToggleField>
            </>
        )
        : <Card.Text><strong>Meeting Agenda</strong>: {meeting.agenda ? meeting.agenda : 'None'}</Card.Text>;

    // Meeting actions and info
    const leaveButtonText = inProgress ? 'Cancel My Meeting' : 'Leave the Line';
    const leave = (
        <Button
            variant='link'
            type='button'
            onClick={() => props.onLeaveQueue(meeting)}
            disabled={props.disabled}
            aria-label={leaveButtonText}
        >
            {leaveButtonText}
            {props.disabled && DisabledMessage}
        </Button>
    );

    const joinText = isVideoMeeting && (
        !inProgress
            ? (
                'The host has not created the meeting yet. You will be able to join the meeting once it is created. ' +
                "We'll show a message in this window when it is created -- pay attention to the window so you don't miss it."
            )
            : 'The host has created the meeting. Join it now! The host will join when they are ready for you.'
    );

    const joinedInpersonMeetingText = (
        props.queue.inperson_location === '' 
            ? (
                'The host(s) have not specified an in-person meeting location.'
            )
            : props.queue.inperson_location
    );

    const joinLink = isVideoMeeting && (
        meeting.backend_metadata!.meeting_url
            ? (
                <Button
                    href={meeting.backend_metadata!.meeting_url}
                    target='_blank'
                    variant='warning'
                    className='mr-3'
                    aria-label='Join Meeting'
                    disabled={props.disabled}
                >
                    Join Meeting
                </Button>
            )
            : <span><strong>Please wait. A Join Meeting button will appear here.</strong></span>
    );

    const meetingInfo = isVideoMeeting && <VideoMeetingInfo metadata={meeting.backend_metadata!} backend={meetingBackend} />;

    return (
        <>
        {closedAlert}
        {turnAlert}
        <h3>{headText}</h3>
        <Card className='card-middle card-width center-align'>
            <Card.Body>
                {meeting.line_place !== null && <Card.Text><strong>Your Number in Line</strong>: {meeting.line_place + 1}</Card.Text>}
                {notificationBlurb}
                <Card.Text><strong>Time Joined</strong>: <DateTimeDisplay dateTime={props.queue.my_meeting!.created_at}/></Card.Text>
                <Card.Text>
                    <strong>Meeting Via</strong>: {meetingBackend.friendly_name} {!inProgress && changeMeetingType}
                </Card.Text>
                {
                    meetingBackend.name === 'inperson' &&
                    <Card.Text>
                        <strong>Meet At</strong>: {joinedInpersonMeetingText}
                    </Card.Text>
                }
                {agendaBlock}
            </Card.Body>
        </Card>
        {joinText && <p>{joinText}</p>}
        <Row className='mb-3'>
            <Col>
                {joinLink}
                {leave}
            </Col>
        </Row>
        {meetingInfo}
        </>
    );
}

function QueueAttending(props: QueueAttendingProps) {
    const description = props.queue.description.trim()
        && <p className="lead">{props.queue.description.trim()}</p>
    const content = !props.queue.my_meeting
        ? <QueueAttendingNotJoined {...props}/>
        : <QueueAttendingJoined {...props}/>
    const yourQueueAlert = props.queue.hosts.find(h => h.username === props.user.username)
        && (
            <>
            <br/>
            <p className="alert alert-info col-lg">
                This is your queue, you can <Link to={"/manage/" + props.queue.id}>manage it</Link>.
            </p>
            </>
        );
    const footer = (
        <a target="_blank" href="https://documentation.its.umich.edu/node/1833">
            Learn more about using Remote Office Hours Queue as an attendee
        </a>
    );
    return (
        <>
        <h2>Welcome to the {props.queue.name} meeting queue.</h2>
        {description}
        {content}
        {yourQueueAlert}
        <hr/>
        {footer}
        </>
    );
}

interface ChangeMeetingTypeDialogProps {
    queue: QueueAttendee;
    backends: MeetingBackend[];
    selectedBackend: string;
    show: boolean;
    onClose: () => void;
    onSubmit: (oldBackend: string) => void;
    onChangeBackend: (backend: string) => void;
}

const ChangeMeetingTypeDialog = (props: ChangeMeetingTypeDialogProps) => {
    const handleSubmit = () => {
        props.onClose();
        props.onSubmit(props.queue.my_meeting?.backend_type as string);
    }
    return (
        <Modal show={props.show} onHide={props.onClose}>
            <Modal.Header closeButton>
                <Modal.Title>Change Meeting Type</Modal.Title>
            </Modal.Header>
            <Modal.Body>
                <div className="row col-lg">
                    <p>Select Meeting Type</p>
                    <p className="required">*</p>
                </div>
                <BackendSelector backends={props.backends}
                    allowedBackends={new Set(props.queue.allowed_backends)}
                    onChange={props.onChangeBackend}
                    selectedBackend={props.selectedBackend}/>
            </Modal.Body>
            <Modal.Footer>
                <Button variant="secondary" onClick={props.onClose}>Cancel</Button>
                <Button variant="primary" onClick={handleSubmit}>OK</Button>
            </Modal.Footer>
        </Modal>
    );
}

interface QueuePageParams {
    queue_id: string;
}

export function QueuePage(props: PageProps<QueuePageParams>) {
    if (!props.user) {
        redirectToLogin(props.loginUrl);
    }
    const queue_id = props.match.params.queue_id;
    if (queue_id === undefined) throw new Error("queue_id is undefined!");
    if (!props.user) throw new Error("user is undefined!");
    const dialogRef = createRef<Dialog>();
    const queueIdParsed = parseInt(queue_id);

    //Setup basic state
    const [selectedBackend, setSelectedBackend] = useState(undefined as string | undefined);
    const [queue, setQueue] = useState(undefined as QueueAttendee | undefined);
    const setQueueWrapped = (q: QueueAttendee | undefined) => {
        if (q) {
            setSelectedBackend(
                q.my_meeting
                    ? q.my_meeting.backend_type
                    : new Set(q.allowed_backends).has(props.defaultBackend)
                        ? props.defaultBackend
                        : Array.from(q.allowed_backends)[0]
            );
        }
        setQueue(q);
    }
    const queueWebSocketError = useQueueWebSocket(queueIdParsed, setQueueWrapped);
    const [myUser, setMyUser] = useState(undefined as MyUser | undefined);
    const userWebSocketError = useUserWebSocket(props.user!.id, (u) => setMyUser(u as MyUser));
    const [showMeetingTypeDialog, setShowMeetingTypeDialog] = useState(false);
    //Setup interactions
    const joinQueue = async (backendType: string) => {
        ReactGA.event({
            category: "Attending",
            action: "Joined Queue",
        });
        await addMeetingAutoAssigned(queue!, props.user!.id, backendType);
    }
    const [doJoinQueue, joinQueueLoading, joinQueueError] = usePromise(joinQueue);
    const leaveQueue = async () => {
        setSelectedBackend(
            new Set(queue!.allowed_backends).has(props.defaultBackend)
                ? props.defaultBackend
                : Array.from(queue!.allowed_backends)[0]
        );
        ReactGA.event({
            category: "Attending",
            action: "Left Queue",
        });
        await api.removeMeeting(queue!.my_meeting!.id);
    }
    const [doLeaveQueue, leaveQueueLoading, leaveQueueError] = usePromise(leaveQueue);
    const confirmLeaveQueue = (queueStatus: 'open' | 'closed', meetingStatus: MeetingStatus) => {
        const dialogParts = (meetingStatus === MeetingStatus.STARTED)
            ? {
                title: 'Cancel Meeting?',
                action: 'cancel the meeting',
                gerund: 'cancelling the meeting',
                consequences: 'you will no longer meet with the queue host(s)'
            } : {
                title: 'Leave Queue?',
                action: 'leave the queue',
                gerund: 'leaving the queue',
                consequences: 'you will lose your place in line'
            };

        const description = (
            `Are you sure you want to ${dialogParts.action}? ` +
            `By ${dialogParts.gerund}, ${dialogParts.consequences}. ` +
            'If you change your mind, you will have to re-join at the end of the line.' +
            (
                queueStatus === 'closed'
                    ? ' Note: The queue is currently closed. You will not be able to re-join until the queue is re-opened.'
                    : ''
            )
        );
        showConfirmation(dialogRef, () => doLeaveQueue(), dialogParts.title, description);
    }
    const leaveAndJoinQueue = async (backendType: string) => {
        ReactGA.event({
            category: "Attending",
            action: "Left Previous Queue and Joined New Queue",
        });
        await api.removeMeeting(myUser!.my_queue!.my_meeting!.id);
        await addMeetingAutoAssigned(queue!, props.user!.id, backendType);
    }
    const [doLeaveAndJoinQueue, leaveAndJoinQueueLoading, leaveAndJoinQueueError] = usePromise(leaveAndJoinQueue);
    const changeAgenda = async (agenda: string) => {
        return await api.changeAgenda(queue!.my_meeting!.id, agenda);
    }
    const [doChangeAgenda, changeAgendaLoading, changeAgendaError] = usePromise(changeAgenda);
    const changeBackendType = async () => {
        const meeting = await api.changeMeetingType(queue!.my_meeting!.id, selectedBackend!);
        setSelectedBackend(meeting.backend_type);
        return meeting;
    }
    const [doChangeBackendType, changeBackendTypeLoading, changeBackendTypeError] = usePromise(changeBackendType);
    
    //Render
    const isChanging = joinQueueLoading || leaveQueueLoading || leaveAndJoinQueueLoading || changeAgendaLoading || changeBackendTypeLoading;
    const errorSources = [
        {source: 'Queue Connection', error: queueWebSocketError}, 
        {source: 'Join Queue', error: joinQueueError}, 
        {source: 'Leave Queue', error: leaveQueueError}, 
        {source: 'User Connection', error: userWebSocketError}, 
        {source: 'Leave and Join Queue', error: leaveAndJoinQueueError}, 
        {source: 'Change Agenda', error: changeAgendaError},
        {source: 'Change Meeting Type', error: changeBackendTypeError}
    ].filter(e => e.error) as FormError[];
    const loginDialogVisible = errorSources.some(checkForbiddenError);
    const loadingDisplay = <LoadingDisplay loading={isChanging}/>
    const errorDisplay = <ErrorDisplay formErrors={errorSources}/>
    const queueDisplay = queue && selectedBackend
        && (
            <QueueAttending
                queue={queue}
                backends={props.backends}
                user={props.user}
                joinedQueue={myUser?.my_queue}
                disabled={isChanging}
                onJoinQueue={doJoinQueue}
                onLeaveQueue={(myMeeting: Meeting) => confirmLeaveQueue(queue.status, myMeeting.status)}
                onLeaveAndJoinQueue={doLeaveAndJoinQueue}
                onChangeAgenda={doChangeAgenda}
                onShowDialog={() => setShowMeetingTypeDialog(true)}
                onChangeBackendType={doChangeBackendType}
                selectedBackend={selectedBackend}
                onChangeBackend={setSelectedBackend}
            />
        );
    const meetingTypeDialog = queue && selectedBackend
        && <ChangeMeetingTypeDialog queue={queue} backends={props.backends} show={showMeetingTypeDialog} 
            onClose={() => setShowMeetingTypeDialog(false)} 
            onSubmit={doChangeBackendType} selectedBackend={selectedBackend} 
            onChangeBackend={setSelectedBackend}/>
    return (
        <div>
            <Dialog ref={dialogRef}/>
            <LoginDialog visible={loginDialogVisible} loginUrl={props.loginUrl}/>
            {meetingTypeDialog}
            <Breadcrumbs currentPageTitle={queue?.name ?? queueIdParsed.toString()}/>
            {loadingDisplay}
            {errorDisplay}
            {queueDisplay}
        </div>
    );
}