import axios, { AxiosInstance, AxiosResponse } from "axios";
import React, { Context, Dispatch, ReactNode, createContext, useContext, useState } from "react";
import { useLocation, Navigate } from "react-router-dom";
import Cookies from 'js-cookie'
import { Box, Button, ButtonGroup, ButtonOwnProps, Modal, PaletteMode, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField } from "@mui/material";
import { Form } from "@rjsf/mui";
import validator from '@rjsf/validator-ajv8';
import { blue, grey } from "@mui/material/colors";
import { User } from "./interfaces";
import { IChangeEvent } from "@rjsf/core";
import { RJSFSchema, WidgetProps } from "@rjsf/utils";
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import Grow from '@mui/material/Grow';
import Popper from '@mui/material/Popper';
import MenuItem from '@mui/material/MenuItem';
import MenuList from '@mui/material/MenuList';
import { Permission } from "./actions";
import TerminalComponent from "./terminal";

export const apiAuthenticatedContext: Context<[boolean, Dispatch<boolean>]> = createContext([false, (value: boolean) => {}] as [boolean, Dispatch<boolean>])


export const formModalStyle = {
    position: 'absolute' as 'absolute',
    maxHeight: "90%",
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',
    width: '70%',
    bgcolor: 'background.paper',
    p: 4,
    borderRadius: 3,
}
export const terminalModalStyle = {
    position: 'absolute' as 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',
    width: '70%',
    bgcolor: 'background.paper',
    p: 1,
    borderRadius: 3,
}

export const getDesignTokens = (mode: PaletteMode) => ({
    palette: {
        mode,
        ...(mode === 'light'
            ? {
                // palette values for light mode
                primary: blue,
                divider: blue[200],
                background: {
                    default: grey[200],
                    light: grey[100],
                },
                text: {
                    primary: grey[900],
                    secondary: grey[800],
                },
            }
            : {
                // palette values for dark mode
                primary: grey,
                divider: grey[700],
                background: {
                    default: grey[900],
                    paper: grey[900],
                    light: grey[700],
                },
                text: {
                    primary: '#fff',
                    secondary: grey[500],
                },
            }),
    },
    components: {
        MuiTypography: {
            defaultProps: {
                color: 'text.primary'
            }
        },
    }
});

const API_URL = `${process.env.REACT_APP_API_SCHEME}://${process.env.REACT_APP_API_URL}`

axios.defaults.withCredentials = true

export const api: AxiosInstance = axios.create({
    baseURL: API_URL,
    withCredentials: true,
});

export function ApiWrapper(p: { children: ReactNode}) {
    const {children} = p
    const token = Cookies.get('auth')

    const [apiAuthenticated, setApiAuthenticated] = useState(Boolean(token))
    if (!apiAuthenticated) {
        Cookies.remove('auth')
    }
    const path = useLocation()
    return (<apiAuthenticatedContext.Provider value={[apiAuthenticated, setApiAuthenticated]}>
        {children}
        {!apiAuthenticated && (path.pathname !== '/login' && path.pathname !== '/signup') && <Navigate to='/login' />}
    </apiAuthenticatedContext.Provider>)
}

export interface ActionInfo {
    name: string
    requestType: 'post' | 'get' | 'delete' | 'patch'
    endpoint: string
    args: {}
    permissions?: number
    response_action?: 'Ignore' | 'Browse' | 'Terminal'
    ServerState?: 'on' | 'off'
}


interface Options{
    label: string
    const: string
    }

export const actionIdentifierContext: Context<string> = createContext('')


function convertNumber(permissions: number): number[]{
    var arr: number[] = []
    if (permissions&Permission.Start){
        arr.push(Permission.Start)
    }
    if (permissions&Permission.Stop){
        arr.push(Permission.Stop)
    }
    if (permissions&Permission.Browse){
        arr.push(Permission.Browse)
    }
    if (permissions&Permission.RunCommand){
        arr.push(Permission.RunCommand)
    }
    if (permissions&Permission.Create){
        arr.push(Permission.Create)
    }
    if (permissions&Permission.Delete){
        arr.push(Permission.Delete)
    }
    if (permissions&Permission.Admin){
        arr.push(Permission.Admin)
    }
    return arr
}

function CustomField(props: WidgetProps){
    const jp = require('jsonpath')
    const [options2, setOptions]: [Options[]|null, Dispatch<Options[]>] = useState(null as Options[]|null)
    const {schema, registry, options, ...newProps} = props
    const {SelectWidget, CheckboxesWidget} = registry.widgets

    if (!schema.fetch_url){
        if (!schema.permissions){
            return <TextField onChange={(event)=>(props.onChange(event.target.value))} value={props.value} label={props.label}/>
        }
        
        return <CheckboxesWidget 
        {...newProps} 
        onChange={(event)=>{
            props.onChange(event.reduce((partialSum: number, a: number) => (partialSum + a), 0))
        }
    } 
    schema={{}} 
    options={{
        enumOptions: [
            {label: 'Start', value: Permission.Start},
            {label: 'Stop', value: Permission.Stop},
            {label: 'Browse', value: Permission.Browse},
            {label: 'Delete', value: Permission.Delete},
            {label: 'Run Command', value: Permission.RunCommand},
            {label: 'Create', value: Permission.Create},
            {label: 'Admin', value: Permission.Admin},
            {label: 'Cloud', value: Permission.Cloud},
        ]}} registry={registry} value={convertNumber(props.value)} />
    }
    
    if (options2 === null){
        api.get(schema.fetch_url as string).then((event)=>{
            let newOptions: Options[] = []
            for (let response of event.data){
                newOptions.push({
                    const: jp.query(response, `$.${schema.fetch_key_path}`).join(' '), 
                    label: jp.query(response, `$.${schema.fetch_display_path}`).join(' '),
                })
            }

            setOptions(newOptions)
        })
    }
    return <SelectWidget {...newProps} schema={{oneOf: options2?options2:[]}} registry={registry} options={{enumOptions: (options2?options2:[]).map((value: Options)=>({label: value.label, value: value.const}))}}/>
}


function isUserAllowed(user: User|null, action: ActionInfo): boolean{
    if (user === null){
        return false
    }

    const isAdmin = (user.Permissions & Permission.Admin) === Permission.Admin
    if (isAdmin){
        return true
    }

    if (!action.permissions){
        return true
    }

    if ((action.permissions & user.Permissions) == action.permissions){
        return true
    }

    return false
}


export function ActionItem(p: { action: ActionInfo, identifierSubstring?: string, sx?: ButtonOwnProps, variant?: any, onClick?: Function }) {
    const actionIdentifier: string = useContext(actionIdentifierContext)
    const identifierSubstring = (typeof p.identifierSubstring !== 'undefined') ? p.identifierSubstring : ''
    const user = useContext(UserInfoContext)

    const [form, setForm] = useState(false);
    const [terminal, setTerminal] = useState(null as string|null);
    const [formData, setFormData]: [RJSFSchema, Dispatch<RJSFSchema>] = useState({})
    const url = p.action.endpoint.replaceAll(`{${identifierSubstring}}`, actionIdentifier)


    function handleSubmit() {
        let promise: Promise<AxiosResponse<any, any>>|null = null
        switch (p.action.requestType) {
            case 'post': {
                promise = api.post(url, formData)
                break
            }
            case 'patch':{
                promise = api.patch(url, formData)
                break
            }
            case 'get': {
                if (formData){
                    console.warn('get can get no arguments, dropping')
                }
                promise = api.get(url)
                break
            }
            case 'delete': {
                if (formData){
                    console.warn('delete can get no arguments, dropping')
                }
                promise = api.delete(url)
                break
            }
        }
        switch (p.action.response_action){
            case 'Browse':{
                if (promise !== null){
                    promise.then((event)=>{window.open(`https://${event.data}`)})
                }
            }
        }
        setForm(false)
        setFormData({})
    }

    function onFormChange(args: IChangeEvent<any, RJSFSchema, any>) {
        setFormData(args.formData)
    }

    return (<>
        <Button variant={p.variant} disabled={!isUserAllowed(user, p.action)} onClick={() => { if (p.onClick) { p.onClick() } p.action.response_action == 'Terminal'?setTerminal(`ws${API_URL.slice("http".length)}${url}`):setForm(true) }} sx={p.sx}>{p.action.name}</Button >
        <Modal
            onClose={() => { setForm(false); setFormData({}); }}
            open={form}
        >
            <Box sx={{ ...formModalStyle}}>
                <Form validator={validator} widgets={{TextWidget: CustomField}} schema={p.action.args} onChange={onFormChange} formData={formData} onSubmit={handleSubmit} />
            </Box>
        </Modal>
        <Modal
            onClose={() => { setTerminal(null); }}
            open={terminal != null}
        >
            <Box sx={terminalModalStyle}>
                <TerminalComponent websocket={terminal} />
            </Box>
        </Modal>
    </>)
}

export function ActionGroup(p: { actions: ActionInfo[], identifierSubstring?: string, children?: ReactNode}) {
    const actionItems: any[] = p.actions.map((action, index, array) => (<ActionItem action={action} identifierSubstring={p.identifierSubstring} /> ))
    const [open, setOpen] = React.useState(false);
    const anchorRef = React.useRef<HTMLDivElement>(null);
    const [selectedIndex, setSelectedIndex] = React.useState(0);
    const user = useContext(UserInfoContext)
    
    for (let child of React.Children.toArray(p.children)){
        actionItems.push(child)
    }

    const handleMenuItemClick = (
        event: React.MouseEvent<HTMLLIElement, MouseEvent>,
        index: number,
      ) => {
        setSelectedIndex(index);
        setOpen(false);
    };

    const handleToggle = () => {
      setOpen((prevOpen) => !prevOpen);
    };

    const handleClose = (event: Event) => {
      if (
        anchorRef.current &&
        anchorRef.current.contains(event.target as HTMLElement)
      ) {
        return;
      }
  
      setOpen(false);
    }
    return (
        <React.Fragment>
          <ButtonGroup variant="outlined" ref={anchorRef} aria-label="split button">
            {actionItems[selectedIndex]}
            <Button
          size="small"
          aria-controls={open ? 'split-button-menu' : undefined}
          aria-expanded={open ? 'true' : undefined}
          aria-label="select merge strategy"
          aria-haspopup="menu"
          onClick={handleToggle}
        >
          <ArrowDropDownIcon />
        </Button>
          </ButtonGroup>
          <Popper
            sx={{
              zIndex: 1,
            }}
            open={open}
            anchorEl={anchorRef.current}
            role={undefined}
            transition
            disablePortal
          >
            {({ TransitionProps, placement }) => (
              <Grow
                {...TransitionProps}
                style={{
                  transformOrigin:
                    placement === 'bottom' ? 'center top' : 'center bottom',
                }}
              >
                <Paper>
                  <ClickAwayListener onClickAway={handleClose}>
                    <MenuList id="split-button-menu" autoFocusItem>
                      {actionItems.map((option, index) => {
                        return <MenuItem
                          key={option.props.action.name}
                          selected={index === selectedIndex}
                          onClick={(event) => handleMenuItemClick(event, index)}
                          disabled={!isUserAllowed(user, option.props.action)}
                        >
                          {option.props.action.name}
                        </MenuItem>
                        }
                        )
                        }
                    </MenuList>
                  </ClickAwayListener>
                </Paper>
              </Grow>
            )}
          </Popper>
        </React.Fragment>
      );
}


export function DataTable(props: { headers: string[], children: ReactNode, actionInfo?: ActionInfo, actionHook?: Function }) {
    const { children, headers, actionInfo, actionHook } = props
    return <Box padding={4} overflow='clip'>
        <TableContainer component={Paper} sx={{maxHeight: '80svh'}}>
            <Table stickyHeader>
                <TableHead>
                    <TableRow sx={{ backgroundColor: 'background.light', fontWeight: 'bold' }}>
                        {headers.map((value, index, array) => (<TableCell sx={{ backgroundColor: 'background.light', fontWeight: 'bold' }}>{value}</TableCell>))}
                    </TableRow>
                </TableHead>
                <TableBody>
                    {children}
                </TableBody>
            </Table>
        </TableContainer>
        {(actionInfo && <Box marginTop={2} overflow='clip'>
            <ActionItem variant="contained" action={actionInfo} onClick={actionHook} />
        </Box>)}
    </Box>
}


export const UserInfoContext: Context<User|null> = createContext(null as User|null)


export function GlobalUserInfo(props: {children: any}){
    const [user, setUser]: [User|null, Dispatch<User|null>] = useState(null as User|null)
    const [apiAuthenticated, _] = useContext(apiAuthenticatedContext)



    if (user === null && apiAuthenticated){
        api.get('/users/@me').then((event)=>{setUser(event.data)}).catch(()=>{setUser(null)})
      }

      return <UserInfoContext.Provider value={user}>
            {props.children}
      </UserInfoContext.Provider>
}
