import { exec, execSync } from 'child_process'
import * as os from 'os'
import { NetworkConfiguration } from '../../common/SystemTypes'
import { LogManager } from '../LogManager'
import { LogLevel } from '../../common/LogTypes'


export interface NetworkService {
    GetIpAddress (): string
    GetConfiguration (): NetworkConfiguration
    UpdateConfiguration (config: NetworkConfiguration): void
    GetDateTime (): string
    SetDateTime (value: string): void
    EnableWireguard (enabled: boolean): void
    GetWireguardEnabledAsync(): Promise<boolean>
    GetWireguardStatusAsync(): Promise<boolean>
}

let nwServiceSingleton: NetworkService | null = null

export function GetNetworkService (logger: LogManager): NetworkService {
    if (nwServiceSingleton === null) {
        nwServiceSingleton = (os.platform() === 'linux') ? new RpiNetworkService(logger) : new EmulatorNetworkService()
    }

    return nwServiceSingleton
}

class EmulatorNetworkService implements NetworkService {
    private config: NetworkConfiguration = {
        ip: this.GetIpAddress(),
        netmask: 24,
        gateway: '192.168.0.1',
        nameserver: ''
    }
    public GetIpAddress(): string {
        return '192.168.0.100'
    }

    public GetConfiguration(): NetworkConfiguration {
        return this.config
    }
    
    public UpdateConfiguration (config: NetworkConfiguration) {
        Object.assign(this.config, config)
    }
    public GetDateTime (): string {
        return ''
    }
    public SetDateTime (value: string): void {
        console.log(value)
    }
    public EnableWireguard(_enabled: boolean): void {        
    }
    public GetWireguardEnabledAsync(): Promise<boolean> {
        return Promise.resolve(false)
    }
    public GetWireguardStatusAsync(): Promise<boolean> {
        return Promise.resolve(false)
    }
}

class RpiNetworkService implements NetworkService {

    private config: NetworkConfiguration = {
        ip: '',
        netmask: 0,
        gateway: '',
        nameserver: ''
    }

    private logger: LogManager

    constructor(logger: LogManager) {
        this.logger = logger
        this.readNetworkSettings()
    }

    private readNetworkSettings(): void {
        exec('nmcli --terse connection show eth0', { encoding: 'utf-8' }, (err, data) => {
            if (err) {
                console.log(err);
            } else {
                const ip_match = /^ipv4\.addresses\:([\d\.]+)\/(\d+)\s*$/gm.exec(data)
                const gw_match = /^ipv4\.gateway\:([\d\.]+)\s*$/gm.exec(data)
                const ns_match = /^ipv4\.dns\:([\d\.]+)\s*$/gm.exec(data)

                this.config.ip = (ip_match) ? ip_match[1] : ''
                this.config.netmask = (ip_match) ? parseInt(ip_match[2], 10) : 0
                this.config.gateway = (gw_match) ? gw_match[1] : ''
                this.config.nameserver = (ns_match) ? ns_match[1] : ''

                console.log(this.config)
            }
        });
    }

    public GetIpAddress(): string {
        const eth0 = os.networkInterfaces()['eth0']
        if (eth0) {
            return eth0[0]['cidr'] || eth0[0]['address'] || ''
        }
        return ''
    }

    public GetConfiguration(): NetworkConfiguration {
        return this.config
    }
    
    public UpdateConfiguration (config: NetworkConfiguration) {
        if (!config) return
        if (!config.ip || config.ip === "" || !/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(config.ip)) return
        if (!config.netmask || config.netmask < 1 || config.netmask > 31 || parseInt("" + config.netmask, 10) != config.netmask) return
        if (!config.gateway || !/^$|^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(config.gateway)) return
        if (!config.nameserver || !/^$|^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(config.nameserver)) return

        Object.assign(this.config, config)

        exec(`sudo nmcli connection modify eth0 ipv4.method manual ipv4.addr "${config.ip}/${config.netmask}"`, { encoding: 'utf-8' }, (err, _data) => {
            if (err) {
                console.log(err);
            }
            this.readNetworkSettings()
        });
        exec(`sudo nmcli connection modify eth0 ipv4.gateway "${config.gateway}"`, { encoding: 'utf-8' }, (err, _data) => {
            if (err) {
                console.log(err);
            }
            this.readNetworkSettings()
        });
        exec(`sudo nmcli connection modify eth0 ipv4.dns "${config.nameserver}"`, { encoding: 'utf-8' }, (err, _data) => {
            if (err) {
                console.log(err);
            }
            this.readNetworkSettings()
        });
    }
    public GetDateTime (): string {
        try
        {
            const data = execSync('date', { encoding: 'utf-8' })
            return data
        }
        catch (e) {
            console.log(e)
            return 'error'
        }
    }
    public SetDateTime (value: string): void {
        try
        {
            const d = new Date(value)
            const execString = `date -s "${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}"`
            const t = execSync(execString, { encoding: 'utf-8' })
            this.logger.log(`Set date/time: "${t}"`, LogLevel.Info, { type: 'SYSTEM', id: '' })
        }
        catch (e) {
            this.logger.log(`Error setting date/time: "${e}"`, LogLevel.Error, { type: 'SYSTEM', id: '' })
        }
    }
    public EnableWireguard(enabled: boolean): void {        
        if (enabled) {
            exec('sudo nmcli connection up wg0 && sudo nmcli connection modify wg0 autoconnect yes', (err, _data) => {
                if (err) console.log(err)
            })
        } else {
            exec('sudo nmcli connection down wg0 && sudo nmcli connection modify wg0 autoconnect no', (err, _data) => {
                if (err) console.log(err)
            })            
        }
    }
    public GetWireguardEnabledAsync(): Promise<boolean> {
        return new Promise<boolean>((resolve, _) => {
            exec('sudo nmcli -f GENERAL.STATE -t device show wg0', { encoding: 'utf-8' }, (err, data) => {
                if (err) {
                    console.log(err)
                    resolve(false)
                    return
                }

                const fields = data.split(':')
                if (fields.length > 1) {
                    resolve(!fields[1].startsWith('30'))
                    return
                }

                resolve(false)
            })
        })
    }
    public GetWireguardStatusAsync(): Promise<boolean> {
        return new Promise<boolean>((resolve, _) => {
            exec('ping -q -c 3 -I wg0 10.198.79.1 > /dev/null', { encoding: 'utf-8' }, (err, _) => {
                if (err) {
                    console.log(err)
                    resolve(false)
                    return
                }

                resolve(true)
            })
        })
    }
}
