import { create } from 'zustand';

export interface TranscriptionSegment {
  text: string;
  isFinal: boolean;
  start: number;
  end: number;
}

export interface TranscriptionSession {
  id: string;
  user_id: string;
  title: string | null;
  started_at: string;
  ended_at: string | null;
  duration_seconds: number | null;
  timetable_period_id: string | null;
  timetable_event_type: string | null;
  timetable_event_label: string | null;
  auto_tagged: boolean;
  word_count: number;
  segment_count: number;
}

export interface ServerSegment {
  text: string;
  start: number;
  end: number;
}

export interface TimetablePeriod {
  period_id: string | null;
  event_type: string | null;
  event_label: string | null;
  start_time: string | null;
  end_time: string | null;
}

export interface LLMConfig {
  provider: 'openai' | 'anthropic' | 'ollama' | 'openrouter' | 'google';
  model: string;
  apiKey: string;
  baseUrl?: string;      // for Ollama: e.g. https://ollama.kevlarai.com
  whisperModel?: string; // faster-whisper model size sent to WhisperLive
}

export type ExportFormat = 'srt' | 'txt' | 'json';

export interface KeywordWatch {
  id: string;
  user_id: string;
  keyword: string;
  match_type: string;
  action: string;
  is_active: boolean;
  created_at: string;
}

export interface KeywordMatch {
  keyword: string;
  watch_id: string | null;
  segment_text: string;
  elapsed_seconds: number;
  matched_at: string;
}

const LLM_CONFIG_STORAGE_KEY = 'cc_llm_config';

function loadLLMConfig(): LLMConfig {
  try {
    const stored = localStorage.getItem(LLM_CONFIG_STORAGE_KEY);
    if (stored) {
      return JSON.parse(stored);
    }
  } catch (e) {
    console.error('Failed to load LLM config from localStorage:', e);
  }
  return {
    provider: 'openai',
    model: '',
    apiKey: '',
    baseUrl: '',
    whisperModel: 'large-v3',
  };
}

function saveLLMConfig(config: LLMConfig): void {
  try {
    localStorage.setItem(LLM_CONFIG_STORAGE_KEY, JSON.stringify(config));
  } catch (e) {
    console.error('Failed to save LLM Config to localStorage:', e);
  }
}

interface TranscriptionState {
  // Session state
  isRecording: boolean;
  isConnecting: boolean;
  activeSession: TranscriptionSession | null;

  // Live feed
  completedSegments: TranscriptionSegment[];  // segments that scrolled off the server window (archived)
  serverWindow: ServerSegment[];              // the current server-provided segment window (last N)
  currentSegment: TranscriptionSegment | null; // the live (last) segment if still being refined

  // Canvas event buffer (flushed to API every 5s)
  pendingCanvasEvents: any[];

  // Timetable context
  timetableContext: TimetablePeriod | null;

  // UI state
  wordCount: number;
  elapsedSeconds: number;

  // LLM config (stored in localStorage only)
  llmConfig: LLMConfig;

  // Summary state
  summaryText: string | null;
  isGeneratingSummary: boolean;
  summaryError: string | null;

  // Export state
  isExporting: boolean;
  exportError: string | null;

  // Keyword state
  keywordWatches: KeywordWatch[];
  keywordMatches: KeywordMatch[];

  // Auth (set by panel via setAuthInfo after SIGNED_IN)
  _accessToken: string | null;
  _userId: string | null;
  setAuthInfo: (token: string | null, userId: string | null) => void;

  // Actions
  startSession: (timetableTag?: TimetablePeriod) => Promise<void>;
  stopSession: () => Promise<void>;
  updateServerWindow: (segments: ServerSegment[], isLastLive: boolean) => void;
  saveSegment: (text: string, isFinal: boolean, metadata: { start: number; end: number }) => Promise<void>;
  resetSession: () => void;
  tickElapsed: () => void;
  addCanvasEvent: (event: any) => void;
  flushCanvasEvents: () => Promise<void>;
  loadSessions: () => Promise<TranscriptionSession[]>;
  setTimetableContext: (context: TimetablePeriod | null) => void;

  // LLM config actions
  setLLMConfig: (config: Partial<LLMConfig>) => void;
  getLLMConfig: () => LLMConfig;

  // Summary actions
  setSummaryText: (text: string | null) => void;
  setIsGeneratingSummary: (generating: boolean) => void;
  setSummaryError: (error: string | null) => void;

  // Export actions
  exportSession: (sessionId: string, format: ExportFormat) => Promise<void>;
  setExportError: (error: string | null) => void;

  // Keyword actions
  loadKeywordWatches: () => Promise<void>;
  addKeywordWatch: (keyword: string) => Promise<void>;
  deleteKeywordWatch: (watchId: string) => Promise<void>;
  checkSegmentForKeywords: (text: string, elapsedSeconds: number) => Promise<void>;
  clearKeywordMatches: () => void;
}

export const useTranscriptionStore = create<TranscriptionState>((set, get) => {
  // Direct PostgREST fetch — uses stored _accessToken, no GoTrueClient lock.
  const pgFetch = async <T = any>(
    method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
    table: string,
    options: { body?: object; query?: string; prefer?: string; single?: boolean } = {}
  ): Promise<T | null> => {
    const token = get()._accessToken;
    if (!token) throw new Error('pgFetch: no access token');
    const url = `${import.meta.env.VITE_SUPABASE_URL}/rest/v1/${table}${options.query ? `?${options.query}` : ''}`;
    const headers: Record<string, string> = {
      'Authorization': `Bearer ${token}`,
      'apikey': import.meta.env.VITE_SUPABASE_ANON_KEY,
      'Content-Type': 'application/json',
    };
    if (options.prefer) headers['Prefer'] = options.prefer;
    if (options.single) headers['Accept'] = 'application/vnd.pgrst.object+json';
    const res = await fetch(url, {
      method,
      headers,
      ...(options.body ? { body: JSON.stringify(options.body) } : {}),
    });
    if (!res.ok) {
      const err = await res.text();
      throw new Error(`PostgREST ${res.status}: ${err}`);
    }
    if (res.status === 204) return null;
    return res.json() as Promise<T>;
  };

  return {
    isRecording: false,
    isConnecting: false,
    activeSession: null,
    _accessToken: null,
    _userId: null,
    completedSegments: [],
    serverWindow: [],
    currentSegment: null,
    pendingCanvasEvents: [],
    timetableContext: null,
    wordCount: 0,
    elapsedSeconds: 0,

    // LLM config initialized from localStorage
    llmConfig: loadLLMConfig(),

    // Summary state
    summaryText: null,
    isGeneratingSummary: false,
    summaryError: null,

    // Export state
    isExporting: false,
    exportError: null,

    // Keyword state
    keywordWatches: [],
    keywordMatches: [],

    setTimetableContext: (context) => {
      set({ timetableContext: context });
    },

    setAuthInfo: (token: string | null, userId: string | null) => {
      set({ _accessToken: token, _userId: userId });
    },

    startSession: async (timetableTag?: TimetablePeriod) => {
      set({ isRecording: true, isConnecting: false, elapsedSeconds: 0, timetableContext: timetableTag || null });

      try {
        const { _userId: userId } = get();
        if (!userId) {
          console.error('No authenticated user');
          return;
        }

        const sessionData = {
          user_id: userId,
          title: timetableTag?.event_label || 'Untitled Session',
          canvas_type: 'teaching-canvas',
          timetable_period_id: timetableTag?.period_id || null,
          timetable_event_type: timetableTag?.event_type || null,
          timetable_event_label: timetableTag?.event_label || null,
          auto_tagged: !!timetableTag,
        };

        const data = await pgFetch<TranscriptionSession>('POST', 'transcription_sessions', {
          body: sessionData,
          prefer: 'return=representation',
          single: true,
        });

        if (!data) {
          console.error('Failed to create session: no data returned');
          return;
        }

        set({ activeSession: data });
      } catch (error) {
        console.error('Error starting session:', error);
      }
    },

    stopSession: async () => {
      const { activeSession, currentSegment, completedSegments } = get();

      // The live segment (currentSegment) was never added to completedSegments — flush it now.
      let newCompleted = [...completedSegments];
      if (currentSegment && currentSegment.text.trim()) {
        const alreadyIn = newCompleted.some(s => Math.abs(s.start - currentSegment.start) < 0.5);
        if (!alreadyIn) {
          const idx = newCompleted.length;
          newCompleted.push({ ...currentSegment, isFinal: true });
          if (activeSession) {
            pgFetch('POST', 'transcription_segments', {
              body: {
                session_id: activeSession.id,
                sequence_index: idx,
                text: currentSegment.text,
                start_seconds: currentSegment.start,
                end_seconds: currentSegment.end,
                is_final: true,
              },
            }).catch(err => console.error('Failed to save live segment on stop:', err));
          }
        }
      }

      const finalWordCount = newCompleted.reduce(
        (sum, seg) => sum + seg.text.trim().split(/\s+/).filter(Boolean).length,
        0
      );

      if (activeSession) {
        try {
          await pgFetch('PATCH', 'transcription_sessions', {
            query: `id=eq.${activeSession.id}`,
            body: {
              ended_at: new Date().toISOString(),
              word_count: finalWordCount,
              segment_count: newCompleted.length,
            },
          });
        } catch (error) {
          console.error('Failed to end session:', error);
        }
      }

      set({
        isRecording: false,
        isConnecting: false,
        activeSession: null,
        completedSegments: newCompleted,
        serverWindow: [],
        currentSegment: null,
        wordCount: finalWordCount,
      });
    },

    updateServerWindow: (segments: ServerSegment[], isLastLive: boolean) => {
      const { completedSegments, activeSession } = get();

      if (segments.length === 0) return;

      // The server marks every finalized segment with completed=true and the live
      // one with completed=false. Rather than relying on window-scroll detection
      // (which can miss segments when the server creates several at once), we
      // directly merge every completed segment from this message into the store.
      // This guarantees no gaps: any segment the server says is complete is captured
      // immediately, regardless of how many were created since the last message.
      const serverCompleted = isLastLive ? segments.slice(0, -1) : segments;

      let newCompleted = [...completedSegments];
      const toSave: Array<{ seg: ServerSegment; idx: number }> = [];

      for (const seg of serverCompleted) {
        if (!seg.text.trim()) continue;
        const existingIdx = newCompleted.findIndex(s => Math.abs(s.start - seg.start) < 0.5);
        if (existingIdx >= 0) {
          // Server refined an existing segment — update text and end time in place.
          newCompleted[existingIdx] = {
            ...newCompleted[existingIdx],
            text: seg.text,
            end: seg.end,
          };
        } else {
          const newIdx = newCompleted.length;
          newCompleted.push({ text: seg.text, isFinal: true, start: seg.start, end: seg.end });
          toSave.push({ seg, idx: newIdx });
        }
      }

      // Keep sorted by start time so display order is always correct.
      newCompleted.sort((a, b) => a.start - b.start);

      // Persist and keyword-check only truly new segments.
      if (toSave.length > 0) {
        const elapsed = get().elapsedSeconds;
        for (const { seg, idx } of toSave) {
          if (activeSession) {
            pgFetch('POST', 'transcription_segments', {
              body: {
                session_id: activeSession.id,
                sequence_index: idx,
                text: seg.text,
                start_seconds: seg.start,
                end_seconds: seg.end,
                is_final: true,
              },
            }).catch(err => console.error('Failed to save segment:', err));
          }
          get().checkSegmentForKeywords(seg.text, elapsed);
        }
      }

      const lastSeg = segments[segments.length - 1];
      const newCurrentSegment: TranscriptionSegment | null = isLastLive
        ? { text: lastSeg.text, isFinal: false, start: lastSeg.start, end: lastSeg.end }
        : null;

      const newWordCount = newCompleted.reduce(
        (sum, seg) => sum + seg.text.trim().split(/\s+/).filter(Boolean).length,
        0
      );

      set({
        serverWindow: segments,
        completedSegments: newCompleted,
        currentSegment: newCurrentSegment,
        wordCount: newWordCount,
      });
    },

    saveSegment: async (text: string, isFinal: boolean, metadata: { start: number; end: number }) => {
      const { completedSegments, currentSegment, activeSession } = get();

      if (isFinal) {
        // Deduplicate by start time: if a segment with this start already exists, update it
        // rather than appending. This prevents doubles when the stability timer fires and
        // the segment later appears in the server's finalized list with a slightly extended end.
        const existingIdx = completedSegments.findIndex(
          (s) => Math.abs(s.start - metadata.start) < 0.5
        );

        let newCompleted: TranscriptionSegment[];
        let isNew: boolean;
        if (existingIdx >= 0) {
          newCompleted = completedSegments.map((s, i) =>
            i === existingIdx ? { text, isFinal: true, ...metadata } : s
          );
          isNew = false;
        } else {
          newCompleted = [...completedSegments, { text, isFinal: true, ...metadata }];
          isNew = true;
        }

        const newWordCount = newCompleted.reduce(
          (sum, seg) => sum + seg.text.trim().split(/\s+/).filter(Boolean).length,
          0
        );

        set({ completedSegments: newCompleted, currentSegment: null, wordCount: newWordCount });

        if (isNew && activeSession) {
          try {
            await pgFetch('POST', 'transcription_segments', {
              body: {
                session_id: activeSession.id,
                sequence_index: newCompleted.length - 1,
                text,
                start_seconds: metadata.start,
                end_seconds: metadata.end,
                is_final: true,
              },
            });
          } catch (error) {
            console.error('Failed to save segment:', error);
          }
        }
      } else {
        // In-progress segment. If the start time jumped to a new position, the previous
        // live segment is done — auto-commit it before switching.
        if (currentSegment && metadata.start > currentSegment.start + 0.5 && currentSegment.text.trim()) {
          const autoCompleted = [...completedSegments, { ...currentSegment, isFinal: true }];
          const autoWordCount = autoCompleted.reduce(
            (sum, seg) => sum + seg.text.trim().split(/\s+/).filter(Boolean).length,
            0
          );
          set({ completedSegments: autoCompleted, wordCount: autoWordCount });
          if (activeSession) {
            pgFetch('POST', 'transcription_segments', {
              body: {
                session_id: activeSession.id,
                sequence_index: autoCompleted.length - 1,
                text: currentSegment.text,
                start_seconds: currentSegment.start,
                end_seconds: currentSegment.end,
                is_final: true,
              },
            }).catch(err => console.error('Failed to save auto-committed segment:', err));
          }
        }
        set({ currentSegment: { text, isFinal: false, ...metadata } });
      }
    },

    resetSession: () => {
      set({
        isRecording: false,
        isConnecting: false,
        completedSegments: [],
        serverWindow: [],
        currentSegment: null,
        wordCount: 0,
        elapsedSeconds: 0,
        activeSession: null,
        pendingCanvasEvents: [],
        timetableContext: null,
        keywordMatches: [],
      });
    },

    tickElapsed: () => {
      set((state) => ({ elapsedSeconds: state.elapsedSeconds + 1 }));
    },

    addCanvasEvent: (event) => {
      set((state) => ({
        pendingCanvasEvents: [...state.pendingCanvasEvents, event],
      }));
    },

    flushCanvasEvents: async () => {
      const { pendingCanvasEvents, activeSession } = get();

      if (pendingCanvasEvents.length === 0) return;

      const eventsToFlush = [...pendingCanvasEvents];

      try {
        for (const event of eventsToFlush) {
          await pgFetch('POST', 'canvas_events', {
            body: {
              session_id: activeSession?.id || null,
              user_id: get()._userId || '',
              timestamp: new Date().toISOString(),
              session_elapsed_seconds: event.sessionElapsedSeconds || null,
              event_type: event.eventType,
              event_payload: event.payload || {},
              canvas_snapshot_url: event.snapshotUrl || null,
              tldraw_page_id: event.pageId || null,
              tldraw_shape_ids: event.shapeIds || null,
            },
          });
        }

        set({ pendingCanvasEvents: [] });
      } catch (error) {
        console.error('Failed to flush canvas events:', error);
      }
    },

    loadSessions: async (): Promise<TranscriptionSession[]> => {
      try {
        const { _userId: userId } = get();
        if (!userId) return [];

        const data = await pgFetch<TranscriptionSession[]>('GET', 'transcription_sessions', {
          query: `user_id=eq.${userId}&order=started_at.desc&limit=50&select=*`,
        });

        return data || [];
      } catch (error) {
        console.error('Error loading sessions:', error);
        return [];
      }
    },

    // LLM config actions - persist to localStorage only
    setLLMConfig: (partialConfig: Partial<LLMConfig>) => {
      const current = get().llmConfig;
      const updated = { ...current, ...partialConfig };
      saveLLMConfig(updated);
      set({ llmConfig: updated });
    },

    getLLMConfig: (): LLMConfig => {
      return get().llmConfig;
    },

    // Summary actions
    setSummaryText: (text: string | null) => {
      set({ summaryText: text });
    },

    setIsGeneratingSummary: (generating: boolean) => {
      set({ isGeneratingSummary: generating });
    },

    setSummaryError: (error: string | null) => {
      set({ summaryError: error });
    },

    // Export actions
    exportSession: async (sessionId: string, format: ExportFormat) => {
      set({ isExporting: true, exportError: null });

      try {
        const apiBaseUrl = import.meta.env.VITE_API_BASE || 'https://api.classroomcopilot.ai';
        const response = await fetch(`${apiBaseUrl}/transcribe/sessions/${sessionId}/export`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ format }),
        });

        if (!response.ok) {
          const errorData = await response.json().catch(() => null);
          throw new Error(errorData?.detail || errorData?.error || `Export failed: ${response.status}`);
        }

        // Get filename from Content-Disposition header or use default
        const disposition = response.headers.get('Content-Disposition');
        let filename = `transcription-export.${format}`;
        if (disposition) {
          const match = disposition.match(/filename[*]?=['"\s]*([^;\s]*)/);
          if (match && match[1]) {
            filename = match[1].replace(/["'\\]/g, '');
          }
        }

        // Trigger browser download
        const blob = await response.blob();
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        window.URL.revokeObjectURL(url);
        document.body.removeChild(a);
      } catch (error) {
        console.error('Failed to export session:', error);
        set({ exportError: error instanceof Error ? error.message : 'Failed to export session' });
      } finally {
        set({ isExporting: false });
      }
    },

    setExportError: (error: string | null) => {
      set({ exportError: error });
    },

    loadKeywordWatches: async () => {
      try {
        const { _accessToken: token } = get();
        if (!token) return;
        const apiBaseUrl = import.meta.env.VITE_API_BASE || 'https://api.classroomcopilot.ai';
        const response = await fetch(`${apiBaseUrl}/transcribe/keywords`, {
          headers: { 'Authorization': `Bearer ${token}` },
        });
        if (!response.ok) return;
        const watches = await response.json();
        set({ keywordWatches: watches });
      } catch (error) {
        console.error('Failed to load keyword watches:', error);
      }
    },

    addKeywordWatch: async (keyword: string) => {
      try {
        const { _accessToken: token, _userId: userId } = get();
        if (!token || !userId) return;
        const apiBaseUrl = import.meta.env.VITE_API_BASE || 'https://api.classroomcopilot.ai';
        const response = await fetch(`${apiBaseUrl}/transcribe/keywords`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`,
          },
          body: JSON.stringify({
            user_id: userId,
            keyword: keyword.trim(),
            match_type: 'contains',
            action: 'alert',
          }),
        });
        if (!response.ok) return;
        const newWatch = await response.json();
        set((state) => ({ keywordWatches: [...state.keywordWatches, newWatch] }));
      } catch (error) {
        console.error('Failed to add keyword watch:', error);
      }
    },

    deleteKeywordWatch: async (watchId: string) => {
      try {
        const { _accessToken: token } = get();
        if (!token) return;
        const apiBaseUrl = import.meta.env.VITE_API_BASE || 'https://api.classroomcopilot.ai';
        await fetch(`${apiBaseUrl}/transcribe/keywords/${watchId}`, {
          method: 'DELETE',
          headers: { 'Authorization': `Bearer ${token}` },
        });
        set((state) => ({ keywordWatches: state.keywordWatches.filter((w) => w.id !== watchId) }));
      } catch (error) {
        console.error('Failed to delete keyword watch:', error);
      }
    },

    checkSegmentForKeywords: async (text: string, elapsedSeconds: number) => {
      const { keywordWatches, activeSession } = get();
      if (keywordWatches.length === 0) return;

      const lowerText = text.toLowerCase();
      const matches: KeywordMatch[] = [];

      for (const watch of keywordWatches) {
        if (!watch.is_active) continue;
        const lowerKeyword = watch.keyword.toLowerCase();
        const matched =
          watch.match_type === 'exact'
            ? lowerText === lowerKeyword
            : watch.match_type === 'starts_with'
            ? lowerText.startsWith(lowerKeyword)
            : lowerText.includes(lowerKeyword);

        if (matched) {
          matches.push({
            keyword: watch.keyword,
            watch_id: watch.id,
            segment_text: text,
            elapsed_seconds: elapsedSeconds,
            matched_at: new Date().toISOString(),
          });

          if (activeSession) {
            try {
              const { _accessToken: kwToken } = get();
              const apiBaseUrl = import.meta.env.VITE_API_BASE || 'https://api.classroomcopilot.ai';
              await fetch(`${apiBaseUrl}/transcribe/keywords/events`, {
                method: 'POST',
                headers: {
                  'Content-Type': 'application/json',
                  ...(kwToken ? { 'Authorization': `Bearer ${kwToken}` } : {}),
                },
                body: JSON.stringify({
                  session_id: activeSession.id,
                  keyword_watch_id: watch.id,
                  keyword_text: watch.keyword,
                  matched_in_text: text,
                  session_elapsed_seconds: elapsedSeconds,
                }),
              });
            } catch (error) {
              console.error('Failed to log keyword event:', error);
            }
          }
        }
      }

      if (matches.length > 0) {
        set((state) => ({ keywordMatches: [...state.keywordMatches, ...matches] }));
      }
    },

    clearKeywordMatches: () => {
      set({ keywordMatches: [] });
    },
  };
});
