import React, { useEffect, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Button, Collapse, InputNumber, notification, Space, Typography, Spin, Empty } from 'antd';
import { getLaunchSalesRoster, getRotationSalesTeam, saveRotation, type RotationSource } from '../service';
import type { RotationRow } from '../interface';

const { Panel } = Collapse;

const AVATAR_COLORS = ['#16a34a', '#2563eb', '#7c3aed', '#d97706', '#dc2626', '#0891b2'];

function colorForUser(userId: number): string {
  return AVATAR_COLORS[userId % AVATAR_COLORS.length];
}

function initialsFor(name: string): string {
  const parts = name.trim().split(/\s+/);
  return ((parts[0]?.[0] || '') + (parts[1]?.[0] || '')).toUpperCase();
}

/* A rotation row tagged with which launch it belongs to — rows are grouped
 into one expandable panel per launch so it's clear at a glance who belongs
 to which launch, but Save still sends each launch its own subset of rows. */
interface TaggedRow extends RotationRow {
  launchId: number;
}

interface RowProps {
  row: TaggedRow;
  index: number;
  readOnly?: boolean;
  onLeadsChange: (userId: number, launchId: number, value: number) => void;
}

const Row: React.FC<RowProps> = ({ row, index, readOnly, onLeadsChange }) => {
  return (
    <div className="flex items-center gap-3 rounded-md border border-gray-100 bg-white px-3 py-2 mb-3 shadow-sm">
      <Typography.Text type="secondary" style={{ width: 20 }}>
        {String(index + 1).padStart(2, '0')}
      </Typography.Text>
      <div
        style={{
          width: 28,
          height: 28,
          borderRadius: '50%',
          background: colorForUser(row.user_id),
          color: '#fff',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          fontSize: 12,
          flexShrink: 0,
        }}>
        {initialsFor(row.name)}
      </div>
      <Typography.Text strong style={{ flex: 1 }}>
        {row.name}
      </Typography.Text>
      <Typography.Text type="secondary" style={{ fontSize: 11, width: 80 }}>
        {row.role === 'field-sales' ? 'Field Sales' : row.role === 'pre-sales' ? 'Pre Sales' : 'Team Lead'}
      </Typography.Text>
      <Space size="small" align="center">
        <Typography.Text type="secondary" style={{ fontSize: 12 }}>
          Leads/round
        </Typography.Text>
        <InputNumber
          min={0}
          disabled={readOnly}
          value={row.leads_per_round}
          // 0 is a valid, deliberate "skip this seller" value — `value ?? 0`
          // (not `value || 0`) so a real 0 from the field isn't lost; only an
          // actually-cleared field (null) falls back, to the new floor.
          onChange={(value) => onLeadsChange(row.user_id, row.launchId, value ?? 0)}
          style={{ width: 64, cursor: 'text' }}
        />
      </Space>
    </div>
  );
};

interface RotationListProps {
  launches: Array<{ id: number; name: string }>;
  readOnly?: boolean;
  source: RotationSource;
  campaignId?: string;
  campaignName?: string;
  /** What the rotation is tied to under the campaign: a lead form (Facebook) or a microsite URL (Google). */
  formKey?: string;
  cityId?: number;
  /** Creating a new rotation (vs editing an existing one). */
  isNew?: boolean;
}

/*
  One expandable panel per selected launch — makes which sales person
  belongs to which launch unambiguous, especially once more than one launch
  is added. Selecting a launch appends its own panel, pre-loaded with its
  launch_sales roster (or its already-saved rotation, if one exists) and
  auto-expanded; deselecting a launch removes just its panel. Save still
  sends each launch its own subset of rows to its own endpoint, since the
  backend keeps one independent rotation per launch.

  Shared between Facebook and Google (source prop) — the two differ only in
  which three API calls to make and which field (form_id vs microsite_url)
  carries the mapping key; everything else — layout, the Collapse-per-launch
  grouping, save/validation flow, empty/loading states — is identical, so one
  component here instead of two near-duplicates is what stops them drifting
  out of sync (as they had: Google's copy was missing the per-launch grouping
  entirely before this file absorbed it).
 */
const RotationList: React.FC<RotationListProps> = ({ launches, readOnly, source, campaignId, campaignName, formKey, cityId, isNew }) => {
  const router = useRouter();
  const [loadingLaunchIds, setLoadingLaunchIds] = useState<Set<number>>(new Set());
  const [saving, setSaving] = useState(false);
  const [rows, setRows] = useState<TaggedRow[]>([]);
  const [activeKeys, setActiveKeys] = useState<string[]>([]);
  const prevLaunchIds = useRef<number[]>([]);
  // `saving` state alone isn't enough to block a fast double-click — the
  // re-render that disables the button lags one tick behind the click, so a
  // second click in that window fires a second save request. Guard with a
  // ref instead, which updates synchronously.
  const savingRef = useRef(false);

  const mappingKeyLabel = source === 'google' ? 'microsite' : 'lead form';

  // A rotation belongs to one campaign + form/microsite, so switching either
  // one discards what's loaded and reloads every launch for the new scope.
  const scopeKey = `${source}|${campaignId || ''}|${formKey || ''}`;
  const scopeVersion = useRef(0);
  const prevScopeKey = useRef(scopeKey);

  useEffect(() => {
    if (prevScopeKey.current !== scopeKey) {
      prevScopeKey.current = scopeKey;
      scopeVersion.current += 1;
      prevLaunchIds.current = [];
      setRows([]);
      setLoadingLaunchIds(new Set());
    }
    const version = scopeVersion.current;

    const currentIds = launches.map((l) => l.id);
    const added = currentIds.filter((id) => !prevLaunchIds.current.includes(id));
    const removed = prevLaunchIds.current.filter((id) => !currentIds.includes(id));
    prevLaunchIds.current = currentIds;

    if (removed.length) {
      setRows((prev) => prev.filter((r) => !removed.includes(r.launchId)));
      setActiveKeys((prev) => prev.filter((k) => !removed.includes(Number(k))));
    }

    if (added.length) {
      setActiveKeys((prev) => [...prev, ...added.map(String)]);
      setLoadingLaunchIds((prev) => new Set([...prev, ...added]));

      // Fetched independently per launch (not batched) so each panel shows
      // its own roster as soon as it arrives, instead of waiting on the
      // slowest one before any panel can show data.
      added.forEach((launchId) => {
        (async () => {
          const res: any = campaignId && formKey ? await getRotationSalesTeam(source, launchId, campaignId, formKey) : null;
          let launchRows: TaggedRow[];
          if (res?.rotation?.target?.length) {
            const memberByUserId = new Map((res?.sales_team || []).map((s: any) => [s.field_sales_id, s.field_sales_name]));
            launchRows = res.rotation.target.map((t: any) => ({
              user_id: t.user_id,
              name: memberByUserId.get(t.user_id) || `User #${t.user_id}`,
              role: t.role,
              leads_per_round: t.lead,
              assigned: t.assigned,
              launchId,
            }));
          } else {
            const roster = await getLaunchSalesRoster(launchId, source);
            launchRows = roster.map((r) => ({ ...r, launchId }));
          }
          if (version !== scopeVersion.current) return;
          setRows((prev) => [...prev, ...launchRows]);
          setLoadingLaunchIds((prev) => {
            const next = new Set(prev);
            next.delete(launchId);
            return next;
          });
        })();
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [launches, scopeKey]);

  const handleLeadsChange = (userId: number, launchId: number, value: number): void => {
    setRows((prev) => prev.map((r) => (r.user_id === userId && r.launchId === launchId ? { ...r, leads_per_round: value } : r)));
  };

  const handleSave = (): void => {
    if (savingRef.current) return;
    if (!rows.length) {
      notification.warning({ message: 'Add at least one person to the rotation before saving.' });
      return;
    }
    const emptyLaunches = launches.filter((l) => !rows.some((r) => r.launchId === l.id));
    if (emptyLaunches.length) {
      notification.warning({
        message: 'Launch has no active sales person',
        description: `Deselect these launches, or add an active sales person to their team first: ${emptyLaunches.map((l) => l.name).join(', ')}`,
      });
      return;
    }
    if (!formKey) {
      notification.warning({ message: `Select a ${mappingKeyLabel} before saving the rotation.` });
      return;
    }
    if (!campaignId || !cityId) {
      notification.error({ message: 'Error', description: 'Missing campaign or city — cannot save.' });
      return;
    }

    savingRef.current = true;
    setSaving(true);
    const byLaunch = new Map<number, TaggedRow[]>();
    for (const row of rows) {
      byLaunch.set(row.launchId, [...(byLaunch.get(row.launchId) || []), row]);
    }
    // Selection order, not fetch-arrival order — the first launch is where a new lead lands.
    const launchIds = launches.map((l) => l.id).filter((id) => byLaunch.has(id));
    const launchPayload = launchIds.map((launchId) => ({
      launch_id: launchId,
      sales_team: (byLaunch.get(launchId) || []).map((r) => ({ user_id: r.user_id, role: r.role, leads_per_round: r.leads_per_round })),
    }));

    // One request, one transaction — the campaign/(form or microsite)→launch
    // mapping (what the incoming-lead webhook reads to route a lead at all)
    // and every launch's sales-team rotation are saved together.
    saveRotation(source, {
      campaign_id: campaignId,
      campaign_name: campaignName,
      form_key: formKey,
      city_id: cityId,
      is_new: !!isNew,
      launches: launchPayload,
    })
      .then((res: any) => {
        // The backend leaves inactive/deleted people out of the rotation instead of failing
        // the save — drop them from the list too, and say who was left out.
        const removed: Array<{ user_id: number; name: string }> = res?.data?.removed || [];
        if (removed.length) {
          setRows((prev) => prev.filter((r) => !removed.some((x) => x.user_id === r.user_id)));
        }
        notification.success({
          message: 'Rotation saved.',
          description: removed.length ? `Not added (inactive or blocked): ${removed.map((x) => x.name).join(', ')}` : undefined,
        });
        router.push('/lead-routing');
      })
      .catch((error: any) => {
        notification.error({
          message: 'Error',
          description: error?.response?.data?.message || 'Failed to save rotation.',
        });
      })
      .finally(() => {
        savingRef.current = false;
        setSaving(false);
      });
  };

  return (
    <div className="rounded-lg border border-gray-200 p-4">
      <div className="flex items-center justify-between mb-2">
        <Typography.Text type="secondary" style={{ fontSize: 12 }}>
          ROTATION ORDER · BY LAUNCH
        </Typography.Text>
        {!readOnly && (
          <Button type="primary" style={{ background: '#16a34a', borderColor: '#16a34a' }} loading={saving} onClick={handleSave}>
            Save rotation
          </Button>
        )}
      </div>

      {launches.length === 0 ? (
        <Empty description="No launch selected" image={Empty.PRESENTED_IMAGE_SIMPLE} />
      ) : (
        <Collapse activeKey={activeKeys} onChange={(keys) => setActiveKeys(keys as string[])}>
          {launches.map((launch) => {
            const launchRows = rows.filter((r) => r.launchId === launch.id);
            const isLoading = loadingLaunchIds.has(launch.id);
            return (
              <Panel key={String(launch.id)} header={<Typography.Text strong>{launch.name}</Typography.Text>}>
                {isLoading && launchRows.length === 0 ? (
                  <div className="flex justify-center py-4"><Spin /></div>
                ) : launchRows.length === 0 ? (
                  <Empty description="No one in this rotation yet" image={Empty.PRESENTED_IMAGE_SIMPLE} />
                ) : (
                  launchRows.map((row, index) => (
                    <Row key={`${row.launchId}_${row.user_id}`} row={row} index={index} readOnly={readOnly} onLeadsChange={handleLeadsChange} />
                  ))
                )}
              </Panel>
            );
          })}
        </Collapse>
      )}
    </div>
  );
};

export default RotationList;
