import { useCallback, useEffect, useMemo, useState } from 'react';
import {
  CalendarClock,
  Clock3,
  Loader2,
  MapPin,
  MessageSquare,
  RefreshCw,
  Repeat,
  Timer,
} from 'lucide-react';
import { useSwipeBack } from '@/hooks/useSwipeBack';
import { cancelSchedule, listSchedules, updateSchedule } from '@/services/scheduleService';
import type { ScheduleTask, ScheduleType, UpdateScheduleRequest } from '@/types/schedule';
import { useToast } from '@/components/ui/use-toast';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import {
  EditorPanel,
  EditorPanelEmptyState,
  FieldGroup,
  ItemCard,
  type ItemCardBadge,
  ListPanel,
  type ListItem,
} from '@/components/shared';

interface ScheduleListItem extends ListItem, ScheduleTask {
  id: string;
  display_name: string;
}

interface ScheduleDraft {
  task_id: string;
  room_id: string;
  message: string;
  description: string;
  schedule_type: ScheduleType;
  execute_at_input: string;
  cron_expression: string;
}

type DateParts = {
  year: number;
  month: number;
  day: number;
  hour: number;
  minute: number;
  second: number;
};

function parseDateTimeInput(localDateTime: string): DateParts | null {
  const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/.exec(localDateTime);
  if (!match) return null;

  const [, year, month, day, hour, minute] = match;
  return {
    year: Number(year),
    month: Number(month),
    day: Number(day),
    hour: Number(hour),
    minute: Number(minute),
    second: 0,
  };
}

function formatDateTimeInput(parts: DateParts): string {
  const year = String(parts.year).padStart(4, '0');
  const month = String(parts.month).padStart(2, '0');
  const day = String(parts.day).padStart(2, '0');
  const hour = String(parts.hour).padStart(2, '0');
  const minute = String(parts.minute).padStart(2, '0');
  return `${year}-${month}-${day}T${hour}:${minute}`;
}

function getDatePartsInTimezone(date: Date, timezone: string): DateParts | null {
  try {
    const formatter = new Intl.DateTimeFormat('en-US', {
      timeZone: timezone,
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hour12: false,
    });
    const parts = formatter.formatToParts(date);
    const map = new Map(parts.map(part => [part.type, part.value]));
    const year = Number(map.get('year'));
    const month = Number(map.get('month'));
    const day = Number(map.get('day'));
    const hour = Number(map.get('hour'));
    const minute = Number(map.get('minute'));
    const second = Number(map.get('second'));

    if ([year, month, day, hour, minute, second].some(value => Number.isNaN(value))) {
      return null;
    }

    return { year, month, day, hour, minute, second };
  } catch {
    return null;
  }
}

function toBrowserLocalDateTimeInput(isoDate: string): string {
  const date = new Date(isoDate);
  return formatDateTimeInput({
    year: date.getFullYear(),
    month: date.getMonth() + 1,
    day: date.getDate(),
    hour: date.getHours(),
    minute: date.getMinutes(),
    second: date.getSeconds(),
  });
}

function toTimezoneDateTimeInput(isoDate: string | null, timezone: string): string {
  if (!isoDate) return '';
  const date = new Date(isoDate);
  if (Number.isNaN(date.getTime())) return '';

  const parts = getDatePartsInTimezone(date, timezone);
  if (!parts) {
    return toBrowserLocalDateTimeInput(isoDate);
  }
  return formatDateTimeInput(parts);
}

function getTimezoneOffsetMs(date: Date, timezone: string): number | null {
  const parts = getDatePartsInTimezone(date, timezone);
  if (!parts) return null;

  const timezoneWallClockAsUtc = Date.UTC(
    parts.year,
    parts.month - 1,
    parts.day,
    parts.hour,
    parts.minute,
    parts.second
  );
  return timezoneWallClockAsUtc - date.getTime();
}

function toUtcIso(localDateTime: string, timezone: string): string | null {
  if (!localDateTime) return null;
  const parsed = parseDateTimeInput(localDateTime);
  if (!parsed) return null;

  const wallClockAsUtc = Date.UTC(
    parsed.year,
    parsed.month - 1,
    parsed.day,
    parsed.hour,
    parsed.minute,
    0
  );
  let utcTimestamp = wallClockAsUtc;

  for (let attempt = 0; attempt < 3; attempt += 1) {
    const offsetMs = getTimezoneOffsetMs(new Date(utcTimestamp), timezone);
    if (offsetMs === null) {
      const localDate = new Date(localDateTime);
      return Number.isNaN(localDate.getTime()) ? null : localDate.toISOString();
    }

    const adjustedUtcTimestamp = wallClockAsUtc - offsetMs;
    if (adjustedUtcTimestamp === utcTimestamp) {
      break;
    }
    utcTimestamp = adjustedUtcTimestamp;
  }

  return new Date(utcTimestamp).toISOString();
}

function formatDateTime(isoDate: string | null, timezone: string): string {
  if (!isoDate) return 'Not set';
  const date = new Date(isoDate);
  if (Number.isNaN(date.getTime())) return 'Invalid date';
  try {
    return new Intl.DateTimeFormat(undefined, {
      dateStyle: 'medium',
      timeStyle: 'short',
      timeZone: timezone,
    }).format(date);
  } catch {
    return new Intl.DateTimeFormat(undefined, {
      dateStyle: 'medium',
      timeStyle: 'short',
    }).format(date);
  }
}

function toDraft(task: ScheduleTask, timezone: string): ScheduleDraft {
  return {
    task_id: task.task_id,
    room_id: task.room_id,
    message: task.message,
    description: task.description,
    schedule_type: task.schedule_type,
    execute_at_input: toTimezoneDateTimeInput(task.execute_at, timezone),
    cron_expression: task.cron_expression ?? '',
  };
}

export function Schedules() {
  const { toast } = useToast();
  const [tasks, setTasks] = useState<ScheduleTask[]>([]);
  const [timezone, setTimezone] = useState('UTC');
  const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
  const [draft, setDraft] = useState<ScheduleDraft | null>(null);
  const [roomFilter, setRoomFilter] = useState<string>('all');
  const [isDirty, setIsDirty] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [isSaving, setIsSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const selectedTask = useMemo(
    () => tasks.find(task => task.task_id === selectedTaskId) ?? null,
    [tasks, selectedTaskId]
  );

  const visibleTasks = useMemo(
    () => (roomFilter === 'all' ? tasks : tasks.filter(task => task.room_id === roomFilter)),
    [tasks, roomFilter]
  );

  const roomOptions = useMemo(() => {
    const uniqueRooms = new Map<string, string>();
    tasks.forEach(task => {
      uniqueRooms.set(task.room_id, task.room_alias || task.room_id);
    });
    return Array.from(uniqueRooms.entries()).sort((a, b) => a[1].localeCompare(b[1]));
  }, [tasks]);

  const load = useCallback(async (showRefreshing = false) => {
    setError(null);
    if (showRefreshing) {
      setIsRefreshing(true);
    } else {
      setIsLoading(true);
    }

    try {
      const response = await listSchedules();
      setTasks(response.tasks);
      setTimezone(response.timezone);
      setSelectedTaskId(current =>
        current && response.tasks.some(task => task.task_id === current) ? current : null
      );
    } catch (e) {
      const message = e instanceof Error ? e.message : 'Failed to load schedules';
      setError(message);
    } finally {
      setIsLoading(false);
      setIsRefreshing(false);
    }
  }, []);

  useEffect(() => {
    void load();
  }, [load]);

  useEffect(() => {
    setDraft(selectedTask ? toDraft(selectedTask, timezone) : null);
    setIsDirty(false);
  }, [selectedTask, timezone]);

  useEffect(() => {
    if (!selectedTaskId) return;
    if (roomFilter === 'all') return;
    if (!visibleTasks.some(task => task.task_id === selectedTaskId)) {
      setSelectedTaskId(null);
    }
  }, [roomFilter, selectedTaskId, visibleTasks]);

  useSwipeBack({
    onSwipeBack: () => setSelectedTaskId(null),
    enabled: !!selectedTaskId && window.innerWidth < 1024,
  });

  const scheduleItems: ScheduleListItem[] = useMemo(
    () =>
      visibleTasks.map(task => ({
        ...task,
        id: task.task_id,
        display_name: task.description || task.message,
      })),
    [visibleTasks]
  );

  const handleDraftChange = <K extends keyof ScheduleDraft>(field: K, value: ScheduleDraft[K]) => {
    if (!draft) return;
    setDraft({ ...draft, [field]: value });
    setIsDirty(true);
  };

  const handleSave = async () => {
    if (!draft || !selectedTask) return;

    const message = draft.message.trim();
    const description = draft.description.trim();

    if (!message) {
      toast({
        title: 'Message required',
        description: 'The prompt message cannot be empty.',
        variant: 'destructive',
      });
      return;
    }

    const payload: UpdateScheduleRequest = {
      room_id: draft.room_id,
      message,
      description: description || message,
      schedule_type: draft.schedule_type,
    };

    if (draft.schedule_type === 'once') {
      const executeAtIso = toUtcIso(draft.execute_at_input, timezone);
      if (!executeAtIso) {
        toast({
          title: 'Time required',
          description: 'Set a valid date and time for one-time schedules.',
          variant: 'destructive',
        });
        return;
      }
      payload.execute_at = executeAtIso;
    } else {
      const cronExpression = draft.cron_expression.trim();
      if (!cronExpression) {
        toast({
          title: 'Cron required',
          description: 'Cron schedules require a cron expression.',
          variant: 'destructive',
        });
        return;
      }
      payload.cron_expression = cronExpression;
    }

    setIsSaving(true);
    try {
      await updateSchedule(selectedTask.task_id, payload);
      await load(true);
      toast({
        title: 'Schedule updated',
        description: `Task ${selectedTask.task_id} has been updated.`,
      });
      setIsDirty(false);
    } catch (e) {
      toast({
        title: 'Failed to update schedule',
        description: e instanceof Error ? e.message : 'Unknown error',
        variant: 'destructive',
      });
    } finally {
      setIsSaving(false);
    }
  };

  const handleCancelTask = async () => {
    if (!selectedTask) return;

    if (!confirm(`Cancel schedule ${selectedTask.task_id}?`)) {
      return;
    }

    setIsSaving(true);
    try {
      await cancelSchedule(selectedTask.task_id, selectedTask.room_id);
      setSelectedTaskId(null);
      await load(true);
      toast({
        title: 'Schedule cancelled',
        description: `Task ${selectedTask.task_id} was cancelled.`,
      });
    } catch (e) {
      toast({
        title: 'Failed to cancel schedule',
        description: e instanceof Error ? e.message : 'Unknown error',
        variant: 'destructive',
      });
    } finally {
      setIsSaving(false);
    }
  };

  const renderScheduleItem = (item: ScheduleListItem, isSelected: boolean) => {
    const nextRunLabel = item.next_run_at
      ? formatDateTime(item.next_run_at, timezone)
      : item.cron_description || 'Recurring';

    const badges: ItemCardBadge[] = [
      {
        content: item.schedule_type === 'once' ? 'One-time' : 'Cron',
        variant: item.schedule_type === 'once' ? 'secondary' : 'outline',
        icon: item.schedule_type === 'once' ? Timer : Repeat,
      },
      {
        content: item.room_alias || item.room_id,
        variant: 'outline',
        icon: MapPin,
      },
      {
        content: nextRunLabel,
        variant: 'secondary',
        icon: Clock3,
      },
    ];

    return (
      <ItemCard
        id={item.task_id}
        title={item.description || item.message}
        description={item.message}
        isSelected={isSelected}
        onClick={setSelectedTaskId}
        badges={badges}
      >
        <p className="mt-2 text-xs text-muted-foreground font-mono">{item.task_id}</p>
      </ItemCard>
    );
  };

  return (
    <div className="h-full flex flex-col gap-3 sm:gap-4">
      <Card className="border border-white/40 dark:border-white/10 bg-white/75 dark:bg-stone-900/50 backdrop-blur-xl">
        <CardContent className="py-3 px-4 sm:py-4 sm:px-5">
          <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
            <div className="flex flex-wrap items-center gap-2">
              <Badge variant="secondary">{tasks.length} scheduled task(s)</Badge>
              <Badge variant="outline">Timezone: {timezone}</Badge>
              {roomFilter !== 'all' && (
                <Badge variant="outline">
                  Room: {roomOptions.find(([id]) => id === roomFilter)?.[1] || roomFilter}
                </Badge>
              )}
            </div>
            <div className="flex items-center gap-2">
              <Select value={roomFilter} onValueChange={setRoomFilter}>
                <SelectTrigger className="w-[200px]">
                  <SelectValue placeholder="Filter by room" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="all">All rooms</SelectItem>
                  {roomOptions.map(([id, label]) => (
                    <SelectItem key={id} value={id}>
                      {label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <Button
                variant="outline"
                size="sm"
                onClick={() => void load(true)}
                disabled={isRefreshing || isLoading}
              >
                {isRefreshing ? (
                  <Loader2 className="h-4 w-4 mr-1 animate-spin" />
                ) : (
                  <RefreshCw className="h-4 w-4 mr-1" />
                )}
                Refresh
              </Button>
            </div>
          </div>
        </CardContent>
      </Card>

      <div className="grid grid-cols-1 lg:grid-cols-12 gap-3 sm:gap-4 flex-1 min-h-0">
        <div
          className={`col-span-1 lg:col-span-4 h-full overflow-hidden ${
            selectedTaskId ? 'hidden lg:block' : 'block'
          }`}
        >
          <ListPanel<ScheduleListItem>
            title="Schedules"
            icon={CalendarClock}
            items={scheduleItems}
            selectedId={selectedTaskId || undefined}
            onItemSelect={setSelectedTaskId}
            renderItem={renderScheduleItem}
            showSearch={true}
            searchPlaceholder="Search schedules..."
            showCreateButton={false}
            emptyIcon={CalendarClock}
            emptyMessage={isLoading ? 'Loading schedules...' : 'No schedules found'}
            emptySubtitle={
              isLoading
                ? 'Fetching scheduled tasks from Matrix state'
                : 'Create one with !schedule in Matrix'
            }
            searchFilter={(item, searchTerm) => {
              const term = searchTerm.toLowerCase();
              return (
                item.task_id.toLowerCase().includes(term) ||
                item.description.toLowerCase().includes(term) ||
                item.message.toLowerCase().includes(term) ||
                item.room_id.toLowerCase().includes(term) ||
                (item.room_alias || '').toLowerCase().includes(term)
              );
            }}
          />
        </div>

        <div
          className={`col-span-1 lg:col-span-8 h-full overflow-hidden ${
            selectedTaskId ? 'block' : 'hidden lg:block'
          }`}
        >
          {error ? (
            <EditorPanelEmptyState icon={CalendarClock} message={error} />
          ) : !selectedTask || !draft ? (
            <EditorPanelEmptyState icon={CalendarClock} message="Select a scheduled task to edit" />
          ) : (
            <EditorPanel
              icon={CalendarClock}
              title="Schedule Details"
              isDirty={isDirty}
              onSave={() => void handleSave()}
              onDelete={() => void handleCancelTask()}
              onBack={() => setSelectedTaskId(null)}
              disableSave={isSaving}
              disableDelete={isSaving}
            >
              <FieldGroup label="Task ID" helperText="Immutable schedule identifier">
                <Input value={draft.task_id} readOnly className="font-mono text-xs" />
              </FieldGroup>

              <FieldGroup label="Room" helperText="Where this scheduled task will execute">
                <Input
                  value={`${selectedTask.room_alias || selectedTask.room_id} (${
                    selectedTask.room_id
                  })`}
                  readOnly
                />
              </FieldGroup>

              <FieldGroup
                label="Prompt Message"
                helperText="This is the prompt posted automatically when the schedule runs."
                htmlFor="schedule-message"
              >
                <Textarea
                  id="schedule-message"
                  value={draft.message}
                  onChange={e => handleDraftChange('message', e.target.value)}
                  rows={5}
                  placeholder="@mindroom_agent your scheduled prompt..."
                />
              </FieldGroup>

              <FieldGroup
                label="Description"
                helperText="Human-readable description used in schedule lists."
                htmlFor="schedule-description"
              >
                <Input
                  id="schedule-description"
                  value={draft.description}
                  onChange={e => handleDraftChange('description', e.target.value)}
                />
              </FieldGroup>

              <FieldGroup
                label="Schedule Type"
                helperText="Schedule type is immutable. Cancel and recreate to change it."
              >
                <Input value={draft.schedule_type === 'once' ? 'One-time' : 'Cron'} readOnly />
              </FieldGroup>

              {draft.schedule_type === 'once' ? (
                <FieldGroup
                  label="Run At"
                  helperText={`One-time execution timestamp (${timezone})`}
                  htmlFor="schedule-once-time"
                >
                  <Input
                    id="schedule-once-time"
                    type="datetime-local"
                    value={draft.execute_at_input}
                    onChange={e => handleDraftChange('execute_at_input', e.target.value)}
                  />
                </FieldGroup>
              ) : (
                <FieldGroup
                  label="Cron Expression"
                  helperText="Format: minute hour day month weekday (for example: 0 9 * * 1-5)"
                  htmlFor="schedule-cron"
                >
                  <Input
                    id="schedule-cron"
                    value={draft.cron_expression}
                    onChange={e => handleDraftChange('cron_expression', e.target.value)}
                    placeholder="0 9 * * *"
                  />
                </FieldGroup>
              )}

              <FieldGroup label="Next Run" helperText="Computed next execution time">
                <div className="rounded-md border px-3 py-2 text-sm text-muted-foreground flex items-center gap-2">
                  <Clock3 className="h-4 w-4" />
                  <span>{formatDateTime(selectedTask.next_run_at, timezone)}</span>
                </div>
              </FieldGroup>

              <FieldGroup label="Created" helperText="Creation metadata">
                <div className="space-y-1 text-sm text-muted-foreground">
                  <p>
                    {selectedTask.created_at
                      ? formatDateTime(selectedTask.created_at, timezone)
                      : 'Unknown'}
                  </p>
                  <p className="font-mono text-xs">
                    {selectedTask.created_by || 'Unknown creator'}
                  </p>
                  <p className="font-mono text-xs">{selectedTask.thread_id || 'No thread ID'}</p>
                </div>
              </FieldGroup>

              <div className="rounded-lg border border-amber-200/60 dark:border-amber-800/60 bg-amber-50/60 dark:bg-amber-900/15 p-3 text-sm text-amber-900 dark:text-amber-200 space-y-2">
                <div className="flex items-start gap-2">
                  <MessageSquare className="h-4 w-4 mt-0.5 shrink-0" />
                  <p>
                    Editing or cancelling here updates running schedules automatically (usually
                    within 30 seconds). You can also manage schedules in Matrix with
                    <span className="font-mono"> !list_schedules</span> and
                    <span className="font-mono"> !cancel_schedule {'<id>'}</span>.
                  </p>
                </div>
                <div className="flex items-start gap-2">
                  <Repeat className="h-4 w-4 mt-0.5 shrink-0" />
                  <p>
                    Changing schedule type is not supported in-place. Cancel this task and create a
                    new one with the desired type.
                  </p>
                </div>
                <div className="flex items-start gap-2">
                  <CalendarClock className="h-4 w-4 mt-0.5 shrink-0" />
                  <p>
                    To create new schedules, use Matrix commands like
                    <span className="font-mono"> !schedule daily at 9am ...</span>.
                  </p>
                </div>
              </div>
            </EditorPanel>
          )}
        </div>
      </div>
    </div>
  );
}
