import React, { useEffect, useState } from 'react';
import { api, validationError } from '../../lib/api';
import { useLocaleSettings } from '../../lib/auth';
import { formatDate, formatMoney, parseAmountInput } from '../../lib/format';
import { useApi, useForm, useList, useSubmit } from '../../lib/hooks';
import { errorMessage, t } from '../../lib/i18n';
import { BillLines, BillStatusBadge, formatBillMonth } from '../../components/Bill';
import MoneyField from '../../components/MoneyField';
import { Actions, Alert, AttachmentButton, Button, Field, FilterBar, FormModal, IconButton, ListView, Loading, Modal, PageHeader, Row, RowLegend } from '../../components/ui';

const STATUSES = ['draft', 'sent', 'pending_review', 'paid'];

/**
 * Monthly bills per contract. A draft collects the rent, utility charges and extra lines until the landlord sends it.
 */
export default function BillsPage() {
    const list = useList('/api/admin/bills', { q: '', status: 'draft' });
    const { locale } = useLocaleSettings();
    const [openId, setOpenId] = useState(null);
    const [creating, setCreating] = useState(false);
    const [notice, setNotice] = useState(null);

    return (
        <>
            <PageHeader title={t('nav.bills')} subtitle={t('bills.subtitle')} actions={<Button onClick={() => setCreating(true)}>{t('bills.new')}</Button>} />
            <FilterBar
                search={list.filters.q}
                onSearch={(q) => list.update({ q })}
                searchPlaceholder={t('bills.searchPlaceholder')}
                filters={[
                    {
                        name: 'status',
                        label: t('common.status'),
                        value: list.filters.status,
                        onChange: (status) => list.update({ status }),
                        options: [...STATUSES.map((status) => ({ value: status, label: t(`bill.status.${status}`) })), { value: '', label: t('common.all') }],
                    },
                ]}
            />
            <Alert kind="success" onDismiss={() => setNotice(null)}>{notice}</Alert>
            <RowLegend statuses={[...STATUSES.map((status) => ({ value: status, label: t(`bill.status.${status}`) })), { value: 'overdue', label: t('bills.overdue') }]} />

            <ListView
                list={list}
                empty={t('bills.empty')}
                columns={[t('contracts.tenant'), t('bills.period'), t('bills.due'), t('bills.total')]}
                renderRow={(bill) => (
                    // A sent bill past its grace days shows as overdue: that is what the landlord has to chase.
                    <Row
                        key={bill.id}
                        status={bill.overdue ? 'overdue' : bill.status}
                        label={bill.overdue ? `${t(`bill.status.${bill.status}`)} · ${t('bills.overdue')}` : t(`bill.status.${bill.status}`)}
                    >
                        <td>
                            <div className="strong">{bill.contract.tenant.fullName}</div>
                            <div className="small muted">
                                {bill.contract.unit.propertyName} · {bill.contract.unit.name}
                            </div>
                        </td>
                        <td className="nowrap">
                            {formatBillMonth(bill.periodStart, locale)}
                            <div className="small muted">{t('bills.periodRange', { start: formatDate(bill.periodStart, locale), end: formatDate(bill.periodEnd, locale) })}</div>
                        </td>
                        <td className="nowrap">
                            {formatDate(bill.dueDate, locale)}
                            <div className="small muted">{t('bills.graceUntil', { date: formatDate(bill.graceUntil, locale) })}</div>
                        </td>
                        <td className="strong nowrap">{formatMoney(bill.total, locale)}</td>
                        <Actions>
                            <IconButton icon="eye" label={t('bills.view')} onClick={() => setOpenId(bill.id)} />
                        </Actions>
                    </Row>
                )}
            />

            {creating && (
                <NewBillForm
                    onClose={() => setCreating(false)}
                    onCreated={(bill) => {
                        setCreating(false);
                        list.update({ status: 'draft' });
                        setOpenId(bill.id);
                    }}
                />
            )}

            {openId && (
                <BillDetail
                    id={openId}
                    onClose={() => {
                        setOpenId(null);
                        list.reload();
                    }}
                    onSent={(bill) => {
                        setOpenId(null);
                        setNotice(t('bills.sentNotice', { name: bill.contract.tenant.fullName }));
                        list.reload();
                    }}
                />
            )}
        </>
    );
}

/**
 * A draft for any period of a contract (a missed month, or one ahead of time). Periods that already have a bill are disabled.
 */
function NewBillForm({ onClose, onCreated }) {
    const { locale } = useLocaleSettings();
    const contracts = useApi(() => api.get('/api/admin/contracts', { perPage: 100 }), []);
    const [contractId, setContractId] = useState('');
    const periods = useApi(() => (contractId ? api.get('/api/admin/bills/periods', { contractId }) : Promise.resolve(null)), [contractId]);
    const [periodStart, setPeriodStart] = useState('');
    const submit = useSubmit();
    const { errors } = submit;

    const billable = (contracts.data?.items || []).filter((contract) => contract.status !== 'draft');
    const selected = periods.data?.items.find((period) => period.periodStart === periodStart);

    // Suggest the first period that has no bill yet.
    useEffect(() => {
        setPeriodStart(periods.data?.items.find((period) => !period.bill)?.periodStart || '');
    }, [periods.data]);

    const save = async () => {
        const result = await submit.run(() => {
            const local = {};
            if (!contractId) local.contractId = t('validation.required');
            if (!periodStart) local.periodStart = t('validation.required');
            if (Object.keys(local).length > 0) throw validationError(local);
            return api.post('/api/admin/bills', { contractId, periodStart });
        });
        if (result.ok) onCreated(result.value);
    };

    return (
        <FormModal title={t('bills.new')} onClose={onClose} onSubmit={save} submit={submit} submitLabel={t('bills.create')}>
            <p className="span-2 muted">{t('bills.newHint')}</p>
            <Field label={t('bills.contract')} error={errors.contractId} className="span-2">
                <select value={contractId} disabled={!contracts.data} onChange={(e) => setContractId(e.target.value)}>
                    <option value="">{contracts.data ? t('common.choose') : t('common.loading')}</option>
                    {billable.map((contract) => (
                        <option key={contract.id} value={contract.id}>
                            {contract.tenant.fullName} · {contract.unit.propertyName} · {contract.unit.name} ({t(`contract.status.${contract.status}`)})
                        </option>
                    ))}
                </select>
            </Field>
            <Field
                label={t('bills.period')}
                error={errors.periodStart}
                hint={selected ? t('bills.dueOn', { date: formatDate(selected.dueDate, locale) }) : undefined}
                className="span-2"
            >
                <select value={periodStart} disabled={!periods.data} onChange={(e) => setPeriodStart(e.target.value)}>
                    <option value="">{contractId ? t('common.choose') : t('bills.chooseContractFirst')}</option>
                    {periods.data?.items.map((period) => (
                        <option key={period.periodStart} value={period.periodStart} disabled={Boolean(period.bill)}>
                            {formatBillMonth(period.periodStart, locale)} · {formatDate(period.periodStart, locale)} – {formatDate(period.periodEnd, locale)}
                            {period.bill ? ` — ${t(`bill.status.${period.bill.status}`)}` : ''}
                        </option>
                    ))}
                </select>
            </Field>
            {periods.error && (
                <div className="span-2">
                    <Alert kind="error">{errorMessage(periods.error)}</Alert>
                </div>
            )}
        </FormModal>
    );
}

function BillDetail({ id, onClose, onSent }) {
    const { locale } = useLocaleSettings();
    const loaded = useApi(() => api.get(`/api/admin/bills/${id}`), [id]);
    const [updated, setUpdated] = useState(null);
    const [busy, setBusy] = useState(null);
    const [error, setError] = useState(null);
    const form = useForm({ description: '', amount: '' });
    const submit = useSubmit();
    const bill = updated || loaded.data;

    const run = async (key, action) => {
        setBusy(key);
        setError(null);
        try {
            const result = await action();
            setUpdated(result);
            return result;
        } catch (err) {
            setError(errorMessage(err));
            return null;
        } finally {
            setBusy(null);
        }
    };

    const addLine = async () => {
        const result = await submit.run(() => {
            const amount = parseAmountInput(form.values.amount, locale);
            const local = {};
            if (!form.values.description.trim()) local.description = t('validation.required');
            if (amount === null) local.amount = t('validation.required');
            if (amount === undefined) local.amount = t('money.invalid');
            if (Object.keys(local).length > 0) throw validationError(local);
            return api.post(`/api/admin/bills/${id}/lines`, { description: form.values.description.trim(), amount });
        });
        if (result.ok) {
            setUpdated(result.value);
            form.setValues({ description: '', amount: '' });
        }
    };

    const send = async () => {
        if (!window.confirm(t('bills.confirmSend', { amount: formatMoney(bill.total, locale), name: bill.contract.tenant.fullName }))) return;
        const sent = await run('send', () => api.post(`/api/admin/bills/${id}/send`));
        if (sent) onSent(sent);
    };

    return (
        <Modal title={bill ? t('bills.detailTitle', { period: formatBillMonth(bill.periodStart, locale) }) : t('nav.bills')} onClose={onClose}>
            {loaded.error && <Alert kind="error">{errorMessage(loaded.error)}</Alert>}
            {!bill && !loaded.error && <Loading />}
            {bill && (
                <div className="bill-detail">
                    <div>
                        <div className="strong">{bill.contract.tenant.fullName}</div>
                        <div className="small muted">
                            {bill.contract.unit.propertyName} · {bill.contract.unit.name}
                        </div>
                        <div className="small muted">
                            {t('bills.dueOn', { date: formatDate(bill.dueDate, locale) })} · {t('bills.graceUntil', { date: formatDate(bill.graceUntil, locale) })}
                        </div>
                        <BillStatusBadge bill={bill} />
                    </div>

                    <Alert kind="error" onDismiss={() => setError(null)}>{error}</Alert>
                    {bill.status === 'draft' && <Alert kind="info">{t('bills.draftHint')}</Alert>}

                    <BillLines
                        bill={bill}
                        busyLineId={busy}
                        onRemove={bill.status === 'draft' ? (line) => run(line.id, () => api.del(`/api/admin/bills/${id}/lines/${line.id}`)) : null}
                    />

                    {bill.payment && (
                        <div className="small">
                            <AttachmentButton attachment={bill.payment.proof} label={t('payments.viewProof')} onError={setError} />
                            {bill.status === 'pending_review' && <span className="muted"> {t('bills.reviewInPayments')}</span>}
                            {bill.payment.status === 'rejected' && <div className="text-danger">{bill.payment.rejectionReason}</div>}
                        </div>
                    )}

                    {bill.status === 'draft' && (
                        <form
                            className="bill-add-line"
                            noValidate
                            onSubmit={(event) => {
                                event.preventDefault();
                                addLine();
                            }}
                        >
                            <h3>{t('bills.addLine')}</h3>
                            <p className="field-hint">{t('bills.addLineHint')}</p>
                            <Alert kind="error">{submit.formError}</Alert>
                            <div className="form-grid">
                                <Field label={t('bills.lineDescription')} error={submit.errors.description}>
                                    <input {...form.bind('description')} maxLength={160} />
                                </Field>
                                <MoneyField
                                    label={t('bills.lineAmount')}
                                    currency={bill.total.currency}
                                    locale={locale}
                                    error={submit.errors.amount}
                                    value={form.values.amount}
                                    onChange={(value) => form.set('amount', value)}
                                />
                            </div>
                            <div className="form-actions">
                                <Button type="submit" variant="ghost" busy={submit.busy}>
                                    {t('bills.addLine')}
                                </Button>
                                <Button busy={busy === 'send'} onClick={send}>
                                    {t('bills.send')}
                                </Button>
                            </div>
                        </form>
                    )}
                </div>
            )}
        </Modal>
    );
}
