import { Fragment, FormEvent, useCallback, useEffect, useState } from 'react';
import Layout from '../../components/Layout';
import JobMap from '../../components/map/JobMap';
import type { MapPoint } from '../../components/map/types';
import { api } from '../../lib/api';
import { useRequireAuth } from '../../lib/auth-context';
import { useDevicesStream } from '../../lib/use-devices-stream';
import { formatRelativeTime } from '../../lib/format';

interface DeviceRow {
  id: string;
  name: string;
  serialNumber: string | null;
  mqttUsername: string | null;
  isActive: boolean;
  disabledReason: string | null;
  lastSeenAt: string | null;
}

interface LocationResult {
  point: MapPoint | null;
  job: { id: string; ticketNumber: string; title: string } | null;
}

interface CertInfo {
  id: string;
  serialNumber: string;
  commonName: string;
  fingerprint: string;
  issuedAt: string;
  expiresAt: string;
}

async function downloadText(path: string, filename: string) {
  const res = await fetch(path);
  if (!res.ok) {
    throw new Error(`Download failed (${res.status})`);
  }
  const text = await res.text();
  const blob = new Blob([text], { type: 'application/x-pem-file' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

export default function DevicesPage() {
  const { user, activeOrg, loading } = useRequireAuth();
  const orgId = activeOrg?.org.id ?? null;
  const isAdmin = activeOrg?.role === 'ORG_ADMIN';

  const [devices, setDevices] = useState<DeviceRow[]>([]);
  const [form, setForm] = useState({ name: '', mqttUsername: '', serialNumber: '' });
  const [disablingId, setDisablingId] = useState<string | null>(null);
  const [disableReason, setDisableReason] = useState('');
  const [notice, setNotice] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const [locationDeviceId, setLocationDeviceId] = useState<string | null>(null);
  const [locationResult, setLocationResult] = useState<LocationResult | null>(null);
  const [locationLoading, setLocationLoading] = useState(false);

  const [certDeviceId, setCertDeviceId] = useState<string | null>(null);
  const [certInfo, setCertInfo] = useState<CertInfo | null>(null);
  const [certLoading, setCertLoading] = useState(false);
  const [certBusy, setCertBusy] = useState(false);

  // Forces "last contact" strings to re-render periodically without refetching devices.
  const [, setTick] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => setTick((t) => t + 1), 30_000);
    return () => clearInterval(timer);
  }, []);

  const reload = useCallback(() => {
    if (!orgId) {
      return;
    }
    api
      .get<DeviceRow[]>(`/api/orgs/${orgId}/devices`)
      .then((rows) => {
        setDevices(rows);
        setError(null);
      })
      .catch((err) => setError(err.message));
  }, [orgId]);

  useEffect(reload, [reload]);

  useDevicesStream(orgId, (evt) => {
    setDevices((rows) =>
      rows.map((d) =>
        d.id === evt.deviceId
          ? {
              ...d,
              lastSeenAt: evt.lastSeenAt ?? d.lastSeenAt,
              isActive: evt.isActive ?? d.isActive,
              disabledReason: evt.isActive !== undefined ? evt.disabledReason ?? null : d.disabledReason,
            }
          : d,
      ),
    );
    if (evt.position && evt.deviceId === locationDeviceId) {
      setLocationResult({ point: evt.position, job: evt.job ?? null });
    }
  });

  const addDevice = async (e: FormEvent) => {
    e.preventDefault();
    try {
      const created = await api.post<DeviceRow & { provisioning: { pointsTopic?: string; note: string } }>(
        `/api/orgs/${orgId}/devices`,
        {
          name: form.name,
          mqttUsername: form.mqttUsername || undefined,
          serialNumber: form.serialNumber || undefined,
        },
      );
      setNotice(
        created.provisioning.pointsTopic
          ? `Device created. It should publish points to ${created.provisioning.pointsTopic}`
          : created.provisioning.note,
      );
      setForm({ name: '', mqttUsername: '', serialNumber: '' });
      reload();
    } catch (err: any) {
      setError(err.message);
    }
  };

  const enable = async (deviceId: string) => {
    try {
      await api.patch(`/api/orgs/${orgId}/devices/${deviceId}`, { isActive: true });
      reload();
    } catch (err: any) {
      setError(err.message);
    }
  };

  const confirmDisable = async (deviceId: string) => {
    try {
      await api.patch(`/api/orgs/${orgId}/devices/${deviceId}`, {
        isActive: false,
        disabledReason: disableReason || undefined,
      });
      setDisablingId(null);
      setDisableReason('');
      reload();
    } catch (err: any) {
      setError(err.message);
    }
  };

  const remove = async (deviceId: string) => {
    try {
      await api.delete(`/api/orgs/${orgId}/devices/${deviceId}`);
      reload();
    } catch (err: any) {
      setError(err.message);
    }
  };

  const toggleLocation = async (deviceId: string) => {
    if (locationDeviceId === deviceId) {
      setLocationDeviceId(null);
      setLocationResult(null);
      return;
    }
    setLocationDeviceId(deviceId);
    setLocationResult(null);
    setLocationLoading(true);
    try {
      const result = await api.get<LocationResult>(`/api/orgs/${orgId}/devices/${deviceId}/location`);
      setLocationResult(result);
    } catch (err: any) {
      setError(err.message);
    } finally {
      setLocationLoading(false);
    }
  };

  const toggleCert = async (deviceId: string) => {
    if (certDeviceId === deviceId) {
      setCertDeviceId(null);
      setCertInfo(null);
      return;
    }
    setCertDeviceId(deviceId);
    setCertInfo(null);
    setCertLoading(true);
    try {
      const result = await api.get<CertInfo>(`/api/orgs/${orgId}/devices/${deviceId}/certificate`);
      setCertInfo(result);
    } catch (err: any) {
      if (err.status !== 404) {
        setError(err.message);
      }
      setCertInfo(null);
    } finally {
      setCertLoading(false);
    }
  };

  const issueCert = async (deviceId: string) => {
    setCertBusy(true);
    try {
      const result = await api.post<CertInfo>(`/api/orgs/${orgId}/devices/${deviceId}/certificate`, {});
      setCertInfo(result);
    } catch (err: any) {
      setError(err.message);
    } finally {
      setCertBusy(false);
    }
  };

  const revokeCert = async (deviceId: string) => {
    setCertBusy(true);
    try {
      await api.delete(`/api/orgs/${orgId}/devices/${deviceId}/certificate`);
      setCertInfo(null);
    } catch (err: any) {
      setError(err.message);
    } finally {
      setCertBusy(false);
    }
  };

  if (loading || !user) {
    return null;
  }

  const columnCount = isAdmin ? 8 : 7;

  return (
    <Layout title="Devices">
      <h1>Devices</h1>
      <p style={{ color: '#666' }}>
        Field devices publish to <code>devices/&lt;mqtt username&gt;/points</code>. Broker credentials are provisioned
        separately for now. A disabled device can check <code>GET /api/devices/&lt;serial&gt;/status</code> for its
        disabled state and reason.
      </p>
      {error && <p style={{ color: '#c62828' }}>{error}</p>}
      {notice && <p style={{ color: '#388e3c' }}>{notice}</p>}

      {isAdmin && (
        <form onSubmit={addDevice} style={{ display: 'flex', gap: '0.5rem', marginBottom: '1.5rem', flexWrap: 'wrap' }}>
          <input
            placeholder="name"
            value={form.name}
            onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
            required
            style={{ padding: '0.4rem' }}
          />
          <input
            placeholder="serial number"
            value={form.serialNumber}
            onChange={(e) => setForm((f) => ({ ...f, serialNumber: e.target.value }))}
            style={{ padding: '0.4rem' }}
          />
          <input
            placeholder="mqtt username (only if it connects itself)"
            value={form.mqttUsername}
            onChange={(e) => setForm((f) => ({ ...f, mqttUsername: e.target.value }))}
            style={{ padding: '0.4rem' }}
          />
          <button type="submit">Add device</button>
        </form>
      )}

      <table style={{ width: '100%', borderCollapse: 'collapse' }}>
        <thead>
          <tr style={{ textAlign: 'left', borderBottom: '2px solid #ddd' }}>
            <th style={{ padding: '0.5rem' }}>Name</th>
            <th>MQTT username</th>
            <th>Serial</th>
            <th>Status</th>
            <th>Last contact</th>
            <th>Location</th>
            <th>Certificate</th>
            {isAdmin && <th />}
          </tr>
        </thead>
        <tbody>
          {devices.map((d) => (
            <Fragment key={d.id}>
              <tr style={{ borderBottom: '1px solid #eee' }}>
                <td style={{ padding: '0.5rem' }}>{d.name}</td>
                <td>{d.mqttUsername ? <code>{d.mqttUsername}</code> : '—'}</td>
                <td>{d.serialNumber ?? '—'}</td>
                <td style={{ color: d.isActive ? '#388e3c' : '#c62828' }}>
                  {d.isActive ? 'active' : 'disabled'}
                  {!d.isActive && d.disabledReason && (
                    <div style={{ color: '#888', fontWeight: 400, fontSize: '0.85rem' }}>{d.disabledReason}</div>
                  )}
                </td>
                <td>{formatRelativeTime(d.lastSeenAt)}</td>
                <td>
                  <button onClick={() => toggleLocation(d.id)}>
                    {locationDeviceId === d.id ? 'Hide' : 'Location'}
                  </button>
                </td>
                <td>
                  <button onClick={() => toggleCert(d.id)}>{certDeviceId === d.id ? 'Hide' : 'Certificate'}</button>
                </td>
                {isAdmin && (
                  <td style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
                    {d.isActive ? (
                      <button onClick={() => setDisablingId(d.id)}>Disable</button>
                    ) : (
                      <button onClick={() => enable(d.id)}>Enable</button>
                    )}{' '}
                    <button onClick={() => remove(d.id)}>Delete</button>
                  </td>
                )}
              </tr>
              {locationDeviceId === d.id && (
                <tr>
                  <td colSpan={columnCount} style={{ padding: '0.75rem', background: '#fafafa' }}>
                    {locationLoading && <p style={{ color: '#666', margin: 0 }}>Loading last known position…</p>}
                    {!locationLoading && locationResult && !locationResult.point && (
                      <p style={{ color: '#888', margin: 0 }}>No location data yet for {d.name}.</p>
                    )}
                    {!locationLoading && locationResult?.point && (
                      <>
                        <p style={{ color: '#666', margin: '0 0 0.5rem' }}>
                          Last position {formatRelativeTime(locationResult.point.recordedAt)}
                          {locationResult.job && <> · job {locationResult.job.ticketNumber} — {locationResult.job.title}</>}
                        </p>
                        <JobMap points={[locationResult.point]} heightPx={320} />
                      </>
                    )}
                  </td>
                </tr>
              )}
              {certDeviceId === d.id && (
                <tr>
                  <td colSpan={columnCount} style={{ padding: '0.75rem', background: '#fafafa' }}>
                    {certLoading && <p style={{ color: '#666', margin: 0 }}>Loading certificate…</p>}
                    {!certLoading && !certInfo && !d.serialNumber && (
                      <p style={{ color: '#888', margin: 0 }}>Set a serial number on {d.name} before issuing a certificate.</p>
                    )}
                    {!certLoading && !certInfo && d.serialNumber && (
                      <>
                        <p style={{ color: '#888', margin: '0 0 0.5rem' }}>No certificate issued for {d.name} yet.</p>
                        <button onClick={() => issueCert(d.id)} disabled={certBusy}>
                          Issue certificate
                        </button>
                      </>
                    )}
                    {!certLoading && certInfo && (
                      <>
                        <p style={{ margin: '0 0 0.5rem' }}>
                          CN <code>{certInfo.commonName}</code> · fingerprint <code>{certInfo.fingerprint}</code>
                          <br />
                          issued {new Date(certInfo.issuedAt).toLocaleString()} · expires{' '}
                          {new Date(certInfo.expiresAt).toLocaleString()}
                        </p>
                        <div style={{ display: 'flex', gap: '0.5rem' }}>
                          <button onClick={() => downloadText(`/api/orgs/${orgId}/devices/${d.id}/certificate/cert`, `${certInfo.serialNumber}.crt`)}>
                            Download cert
                          </button>
                          <button onClick={() => downloadText(`/api/orgs/${orgId}/devices/${d.id}/certificate/key`, `${certInfo.serialNumber}.key`)}>
                            Download key
                          </button>
                          <button onClick={() => revokeCert(d.id)} disabled={certBusy}>
                            Revoke
                          </button>
                        </div>
                      </>
                    )}
                  </td>
                </tr>
              )}
            </Fragment>
          ))}
          {isAdmin &&
            disablingId &&
            devices.some((d) => d.id === disablingId) && (
              <tr>
                <td colSpan={columnCount} style={{ padding: '0.75rem', background: '#fff8f8' }}>
                  <form
                    onSubmit={(e) => {
                      e.preventDefault();
                      confirmDisable(disablingId);
                    }}
                    style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}
                  >
                    <span>Reason for disabling {devices.find((d) => d.id === disablingId)?.name}:</span>
                    <input
                      autoFocus
                      placeholder="e.g. reported lost, billing hold"
                      value={disableReason}
                      onChange={(e) => setDisableReason(e.target.value)}
                      style={{ padding: '0.4rem', flex: 1 }}
                    />
                    <button type="submit">Confirm disable</button>
                    <button
                      type="button"
                      onClick={() => {
                        setDisablingId(null);
                        setDisableReason('');
                      }}
                    >
                      Cancel
                    </button>
                  </form>
                </td>
              </tr>
            )}
        </tbody>
      </table>
    </Layout>
  );
}
