import React from 'react';
import { Select, TextInput } from 'lego-on-react';
import block from 'bem-cn';
import Modal from 'components2/Modal';
import Form from 'components2/Form';
import InlineError from 'components2/InlineError';
import toTextValue from 'lib2/toTextValue';
import encodeBody from 'lib2/encodeBody';
import withCsrfToken from 'lib2/withCsrfToken';
import notify from 'lib2/notify';
import dns from 'api2/dns';
import metrika from 'api2/metrika';
import { i18n } from 'i18n2';

const RecordTypes = [
    'A',
    'AAAA',
    'CNAME',
    'TXT',
    'NS',
    'MX',
    'SRV',
].map(val => ({ val, text: val }));

const ContentMap = {
    A: 'address',
    AAAA: 'address',
    CNAME: 'target',
    TXT: 'strings',
    NS: 'target',
    MX: 'exchange',
    SRV: 'target',
};

const PriorityMap = {
    MX: 'preference',
};

const b = block('edit-dns-record-modal');

function toNumber(s) {
    if (!s) {
        return;
    }

    let n = Number(s);

    return Number.isNaN(n) ? s : n;
}

function toRecord(state) {
    let { type, name, content, priority } = state;
    let record = { type, name, rdata: {} };

    ['id', 'ttl'].forEach(key => {
        if (state[key]) {
            record[key] = toNumber(state[key]);
        }
    });

    if (type === 'SRV') {
        ['port', 'weight'].forEach(key => {
            if (state[key]) {
                record.rdata[key] = toNumber(state[key]);
            }
        });
    }

    if (['MX', 'SRV'].includes(state.type) && priority) {
        record.rdata[PriorityMap[type] || 'priority'] = toNumber(priority);
    }

    if (type === 'TXT') {
        // нарезаем на строки длиной 255 для совместимости со стандартом
        content = content.match(/.{1,255}/g) || [];
    }

    record.rdata[ContentMap[type] || 'content'] = content;

    return record;
}

function toFormState(record) {
    if (!record) {
        record = {};
    }

    let { id, type, name, rdata = {}, ttl } = record;
    let content = rdata[ContentMap[type]];

    if (type === 'TXT' && Array.isArray(content)) {
        content = content.join('');
    }

    return {
        id,
        type: toTextValue(type),
        name: toTextValue(name),
        content: toTextValue(content),
        priority: toTextValue(rdata[PriorityMap[type] || 'priority']),
        weight: toTextValue(rdata.weight),
        port: toTextValue(rdata.port),
        ttl: toTextValue(ttl),
    };
}

function toFormErrors(errors, record) {
    let { name, rdata: rdataErrors = {}, ttl } = errors;
    let formErrors = { name, ttl };

    ['port', 'weight'].forEach(key => {
        formErrors[key] = rdataErrors[key];
    });

    formErrors.content = rdataErrors[ContentMap[record.type]];
    formErrors.priority = rdataErrors[PriorityMap[record.type] || 'priority'];

    return Object
        .entries(formErrors)
        .reduce((acc, [key, value]) => {
            if (!value) {
                return acc;
            }

            if (!Array.isArray(value)) {
                value = [value];
            }

            acc[key] = value
                .map(s => {
                    // превращаем текст ошибки в код
                    let code = s.replace(/[\s\.]+/g, '_').replace(/_$/, '').toLowerCase();

                    return i18n(`backend_errors.dns.${key}.${code}`) ||
                        i18n(`backend_errors.dns.${code}`) ||
                        `${key}.${code}`;
                });

            return acc;
        }, {});
}

export default class EditDnsRecordModal extends React.PureComponent {
    constructor(props) {
        super(props);

        this.state = {
            _saved: {},
        };

        this._submit = this._submit.bind(this);
        this._cancel = this._cancel.bind(this);

        this._onTypeChange = this._onTypeChange.bind(this);
        this._onNameChange = this._onTextChange.bind(this, 'name');
        this._onContentChange = this._onTextChange.bind(this, 'content');
        this._onPriorityChange = this._onTextChange.bind(this, 'priority');
        this._onWeightChange = this._onTextChange.bind(this, 'weight');
        this._onPortChange = this._onTextChange.bind(this, 'port');
        this._onTtlChange = this._onTextChange.bind(this, 'ttl');
    }

    componentDidUpdate(prevProps, prevState) {
        let { record } = this.props;

        if (record !== prevProps.record) {
            let formState = toFormState(record);

            this.setState({
                _saved: formState,
                mode: record ? 'edit' : 'create',
                ...formState,
                errors: null,
            });
        }

        let hasChanges = Object
            .entries(prevState._saved)
            .reduce((acc, [key, value]) => acc || value !== this.state[key], false);

        if (hasChanges !== this.state.hasChanges) {
            this.setState({
                hasChanges: this.state.type && hasChanges,
            });
        }
    }

    componentWillUnmount() {
        this._unmounted = true;
    }

    _submit() {
        let { domain, record, onSubmit } = this.props;

        let canonicalName = `${domain.name}.`;
        let editMode = Boolean(record);
        let submittedRecord = toRecord(this.state);

        this.setState({
            busy: true,
            errors: null,
        });

        metrika.send(
            'Редактор DNS',
            editMode ? 'Обновить запись' : 'Добавить запись',
            'Отправка формы',
            submittedRecord.type
        );

        dns
            // передана запись с id - будет обновлена;
            // передана запись без id - будет создана новая
            .send('POST', withCsrfToken(`/domains/${encodeURIComponent(canonicalName)}/records/`), {
                body: JSON.stringify(encodeBody(submittedRecord)),
            })
            .then(({ ok, body }) => {
                metrika.send(
                    'Редактор DNS',
                    editMode ? 'Обновить запись' : 'Добавить запись',
                    ok ? 'Успешно' : 'Ошибка',
                    submittedRecord.type
                );

                if (!ok) {
                    if (body.errors) {
                        this.setState({
                            errors: toFormErrors(body.errors, submittedRecord),
                        });
                    } else {
                        notify(
                            i18n(`dns_record.status.failed_to_${editMode ? 'update' : 'create'}`, submittedRecord),
                            'error'
                        );
                    }

                    return;
                }

                if (!this._unmounted) {
                    let formState = toFormState(editMode ? submittedRecord : {});

                    this.setState({
                        _saved: formState,
                        ...formState,
                    });
                }

                notify(
                    i18n(`dns_record.status.${editMode ? 'updated' : 'created'}`, submittedRecord),
                    'success'
                );

                if (onSubmit) {
                    onSubmit();
                }
            })
            .finally(() => {
                if (!this._unmounted) {
                    this.setState({ busy: false });
                }
            });
    }

    _cancel() {
        let { onCancel, record } = this.props;
        let editMode = Boolean(record);

        metrika.send(
            'Редактор DNS',
            editMode ? 'Обновить запись' : 'Добавить запись',
            'Отмена'
        );

        if (onCancel) {
            onCancel();
        }
    }

    _onTypeChange(items) {
        this.setState({
            type: items[0],
            errors: null,
        });
    }

    _onTextChange(attr, text) {
        this.setState({
            [attr]: text,
        });
    }

    render() {
        let { props, state } = this;
        let editMode = Boolean(props.record);

        return (
            <Modal
                cls={b({}).mix(props.cls)}
                visible={props.visible}
            >
                <Modal.Title>
                    {i18n(`dns_record.${editMode ? 'edit_record' : 'new_record'}`)}
                </Modal.Title>
                <Modal.CloseButton onClick={props.onCancel} />
                <Modal.Body>
                    <Form cls={b('form')}>
                        <Form.Field
                            name="type"
                            errors={state.errors}
                        >
                            <Form.Label required={!editMode}>
                                {i18n('dns_record.type')}
                            </Form.Label>
                            <Form.Value>
                                <Select
                                    cls={b('select', { type: 'type' })}
                                    width="max"
                                    theme="normal"
                                    size="m"
                                    type="radio"
                                    val={state.type}
                                    items={RecordTypes}
                                    onChange={this._onTypeChange}
                                    disabled={editMode}
                                />
                            </Form.Value>
                        </Form.Field>
                        <Form.Field
                            name="name"
                            errors={state.errors}
                        >
                            <Form.Label required={state.type === 'SRV'}>
                                {i18n('dns_record.name')}
                            </Form.Label>
                            <Form.Value>
                                <TextInput
                                    theme="normal"
                                    size="m"
                                    cls={b('input', { type: 'name' })}
                                    text={state.name}
                                    onChange={this._onNameChange}
                                />
                            </Form.Value>
                        </Form.Field>
                        <Form.Field
                            name="content"
                            errors={state.errors}
                        >
                            <Form.Label required>
                                {i18n('dns_record.content')}
                            </Form.Label>
                            <Form.Value>
                                <TextInput
                                    theme="normal"
                                    size="m"
                                    cls={b('input', { type: 'content' })}
                                    text={state.content}
                                    onChange={this._onContentChange}
                                />
                            </Form.Value>
                        </Form.Field>
                        {['MX', 'SRV'].includes(state.type) && (
                            <Form.Field
                                name="priority"
                                errors={state.errors}
                            >
                                <Form.Label required>
                                    {i18n('dns_record.priority')}
                                </Form.Label>
                                <Form.Value>
                                    <TextInput
                                        theme="normal"
                                        size="m"
                                        cls={b('input', { type: 'priority' })}
                                        text={state.priority}
                                        onChange={this._onPriorityChange}
                                    />
                                </Form.Value>
                            </Form.Field>
                        )}
                        {state.type === 'SRV' && (
                            <Form.Field
                                name="weight"
                                errors={state.errors}
                            >
                                <Form.Label required>
                                    {i18n('dns_record.weight')}
                                </Form.Label>
                                <Form.Value>
                                    <TextInput
                                        theme="normal"
                                        size="m"
                                        cls={b('input', { type: 'weight' })}
                                        text={state.weight}
                                        onChange={this._onWeightChange}
                                    />
                                </Form.Value>
                            </Form.Field>
                        )}
                        {state.type === 'SRV' && (
                            <Form.Field
                                name="port"
                                errors={state.errors}
                            >
                                <Form.Label required>
                                    {i18n('dns_record.port')}
                                </Form.Label>
                                <Form.Value>
                                    <TextInput
                                        theme="normal"
                                        size="m"
                                        cls={b('input', { type: 'port' })}
                                        text={state.port}
                                        onChange={this._onPortChange}
                                    />
                                </Form.Value>
                            </Form.Field>
                        )}
                        <Form.Field
                            name="ttl"
                            errors={state.errors}
                        >
                            <Form.Label required>
                                {i18n('dns_record.ttl')}
                            </Form.Label>
                            <Form.Value>
                                <TextInput
                                    theme="normal"
                                    size="m"
                                    cls={b('input', { type: 'ttl' })}
                                    text={state.ttl}
                                    onChange={this._onTtlChange}
                                />
                            </Form.Value>
                        </Form.Field>
                        {state.error && (
                            <InlineError cls={b('error')}>
                                {state.error}
                            </InlineError>
                        )}
                        <Form.Actions
                            cls={b('actions')}
                            onSubmit={this._submit}
                            onCancel={this._cancel}
                            submitCaption={i18n(`common.action.${editMode ? 'update' : 'create'}`)}
                            submitDisabled={!state.hasChanges}
                        />
                    </Form>
                </Modal.Body>
                <Modal.Busy visible={state.busy} />
            </Modal>
        );
    }
}
