import { Note, APIConfig } from '../types';
import { runtimeFetch } from '../services/runtimeFetch';

export class NextcloudAPI {
  private baseURL: string;
  private serverURL: string;
  private authHeader: string;
  private username: string;

  constructor(config: APIConfig) {
    const url = config.serverURL.replace(/\/$/, '');
    this.serverURL = url;
    this.baseURL = `${url}/index.php/apps/notes/api/v1`;
    this.authHeader = 'Basic ' + btoa(`${config.username}:${config.password}`);
    this.username = config.username;
  }

  private async request<T>(path: string, options: RequestInit = {}): Promise<T> {
    const response = await runtimeFetch(`${this.baseURL}${path}`, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': this.authHeader,
        ...options.headers,
      },
    });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);
    }

    return response.json();
  }

  async fetchNotes(): Promise<Note[]> {
    return this.request<Note[]>('/notes');
  }

  async createNote(title: string, content: string, category: string): Promise<Note> {
    return this.request<Note>('/notes', {
      method: 'POST',
      body: JSON.stringify({ title, content, category, favorite: false }),
    });
  }

  async updateNote(note: Note): Promise<Note> {
    return this.request<Note>(`/notes/${note.id}`, {
      method: 'PUT',
      body: JSON.stringify({
        title: note.title,
        content: note.content,
        category: note.category,
        favorite: note.favorite,
      }),
    });
  }

  async deleteNote(id: number): Promise<void> {
    await this.request<void>(`/notes/${id}`, { method: 'DELETE' });
  }

  // Fetch lightweight note list with IDs and favorites for hybrid sync
  async fetchNotesMetadata(): Promise<Array<{id: number, title: string, category: string, favorite: boolean, modified: number}>> {
    const notes = await this.request<Note[]>('/notes');
    return notes.map(note => ({
      id: note.id as number,
      title: note.title,
      category: note.category,
      favorite: note.favorite,
      modified: note.modified,
    }));
  }

  // Update only favorite status via API
  async updateFavoriteStatus(noteId: number, favorite: boolean): Promise<void> {
    await this.request<Note>(`/notes/${noteId}`, {
      method: 'PUT',
      body: JSON.stringify({ favorite }),
    });
  }

  // Map WebDAV note to API ID by matching modified timestamp and category
  // We can't use title because API title and WebDAV first-line title can differ
  async findApiIdForNote(title: string, category: string, modified: number): Promise<number | null> {
    try {
      const metadata = await this.fetchNotesMetadata();
      
      // First try exact title + category match
      let match = metadata.find(note => 
        note.title === title && note.category === category
      );
      
      // If no title match, try modified timestamp + category (more reliable)
      if (!match) {
        match = metadata.find(note => 
          note.modified === modified && note.category === category
        );
      }
      
      return match ? match.id : null;
    } catch (error) {
      console.error('Failed to find API ID for note:', error);
      return null;
    }
  }

  async fetchAttachment(_noteId: number | string, path: string, noteCategory?: string): Promise<string> {
    // Build WebDAV path: /remote.php/dav/files/{username}/Notes/{category}/.attachments.{noteId}/{filename}
    // The path from markdown is like: .attachments.38479/Screenshot.png
    // We need to construct the full WebDAV URL
    
    let webdavPath = `/remote.php/dav/files/${this.username}/Notes`;
    
    // Add category subfolder if present
    if (noteCategory) {
      webdavPath += `/${noteCategory}`;
    }
    
    // Add the attachment path (already includes .attachments.{id}/filename)
    webdavPath += `/${path}`;
    
    const url = `${this.serverURL}${webdavPath}`;
    console.log(`[Note ${_noteId}] Fetching attachment via WebDAV:`, url);
    
    const response = await runtimeFetch(url, {
      headers: {
        'Authorization': this.authHeader,
      },
    });

    if (!response.ok) {
      throw new Error(`Failed to fetch attachment: ${response.status}`);
    }

    const blob = await response.blob();
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onloadend = () => resolve(reader.result as string);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
  }

  getServerURL(): string {
    return this.serverURL;
  }

  async uploadAttachment(noteId: number | string, file: File, noteCategory?: string): Promise<string> {
    // Create .attachments.{noteId} directory path and upload file via WebDAV PUT
    // Returns the relative path to insert into markdown
    
    let webdavPath = `/remote.php/dav/files/${this.username}/Notes`;
    
    if (noteCategory) {
      webdavPath += `/${noteCategory}`;
    }
    
    // Sanitize note ID: extract just the filename without extension and remove invalid chars
    // noteId might be "category/filename.md" or just "filename.md"
    const noteIdStr = String(noteId);
    const justFilename = noteIdStr.split('/').pop() || noteIdStr;
    const filenameWithoutExt = justFilename.replace(/\.(md|txt)$/, '');
    const sanitizedNoteId = filenameWithoutExt.replace(/[^a-zA-Z0-9_-]/g, '_');
    
    const attachmentDir = `.attachments.${sanitizedNoteId}`;
    const fileName = file.name.replace(/[^a-zA-Z0-9._-]/g, '_'); // Sanitize filename
    const fullPath = `${webdavPath}/${attachmentDir}/${fileName}`;
    
    const url = `${this.serverURL}${fullPath}`;
    console.log('Uploading attachment via WebDAV:', url);
    
    // First, try to create the attachments directory (MKCOL)
    // This may fail if it already exists, which is fine
    try {
      await runtimeFetch(`${this.serverURL}${webdavPath}/${attachmentDir}`, {
        method: 'MKCOL',
        headers: {
          'Authorization': this.authHeader,
        },
      });
    } catch (e) {
      // Directory might already exist, continue
    }
    
    // Read file as ArrayBuffer
    const arrayBuffer = await file.arrayBuffer();
    
    // Upload the file via PUT
    const response = await runtimeFetch(url, {
      method: 'PUT',
      headers: {
        'Authorization': this.authHeader,
        'Content-Type': file.type || 'application/octet-stream',
      },
      body: arrayBuffer,
    });

    if (!response.ok && response.status !== 201 && response.status !== 204) {
      throw new Error(`Failed to upload attachment: ${response.status}`);
    }

    // Return the relative path for markdown
    return `${attachmentDir}/${fileName}`;
  }

  async fetchCategoryColors(): Promise<Record<string, number>> {
    const webdavPath = `/remote.php/dav/files/${this.username}/Notes/.category-colors.json`;
    const url = `${this.serverURL}${webdavPath}`;
    
    try {
      const response = await runtimeFetch(url, {
        headers: {
          'Authorization': this.authHeader,
        },
      });

      if (!response.ok) {
        if (response.status === 404) {
          // File doesn't exist yet, return empty object
          return {};
        }
        throw new Error(`Failed to fetch category colors: ${response.status}`);
      }

      const text = await response.text();
      return JSON.parse(text);
    } catch (error) {
      console.warn('Could not fetch category colors, using empty:', error);
      return {};
    }
  }

  async saveCategoryColors(colors: Record<string, number>): Promise<void> {
    const webdavPath = `/remote.php/dav/files/${this.username}/Notes/.category-colors.json`;
    const url = `${this.serverURL}${webdavPath}`;
    
    const content = JSON.stringify(colors, null, 2);
    
    const response = await runtimeFetch(url, {
      method: 'PUT',
      headers: {
        'Authorization': this.authHeader,
        'Content-Type': 'application/json',
      },
      body: content,
    });

    if (!response.ok && response.status !== 201 && response.status !== 204) {
      throw new Error(`Failed to save category colors: ${response.status}`);
    }
  }

  // WebDAV-based note operations
  private parseNoteFromContent(content: string, filename: string, category: string, etag: string, modified: number): Note {
    // Extract title from first line
    const firstLine = content.split('\n')[0].replace(/^#+\s*/, '').trim();
    const title = firstLine || filename.replace(/\.(md|txt)$/, '');
    
    return {
      id: category ? `${category}/${filename}` : filename,
      filename,
      path: category ? `${category}/${filename}` : filename,
      etag,
      readonly: false,
      content, // Store full content including first line
      title,
      category,
      favorite: false,
      modified,
    };
  }

  private formatNoteContent(note: Note): string {
    // Content already includes the title as first line
    return note.content;
  }

  async fetchNotesWebDAV(): Promise<Note[]> {
    const webdavPath = `/remote.php/dav/files/${this.username}/Notes`;
    const url = `${this.serverURL}${webdavPath}`;
    
    const response = await runtimeFetch(url, {
      method: 'PROPFIND',
      headers: {
        'Authorization': this.authHeader,
        'Depth': 'infinity',
        'Content-Type': 'application/xml',
      },
      body: `<?xml version="1.0"?>
        <d:propfind xmlns:d="DAV:">
          <d:prop>
            <d:getlastmodified/>
            <d:getetag/>
            <d:getcontenttype/>
            <d:resourcetype/>
          </d:prop>
        </d:propfind>`,
    });

    if (!response.ok) {
      throw new Error(`Failed to list notes: ${response.status}`);
    }

    const xmlText = await response.text();
    const notes: Note[] = [];
    
    // Parse XML response
    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(xmlText, 'text/xml');
    const responses = xmlDoc.getElementsByTagNameNS('DAV:', 'response');
    
    for (let i = 0; i < responses.length; i++) {
      const responseNode = responses[i];
      const href = responseNode.getElementsByTagNameNS('DAV:', 'href')[0]?.textContent || '';
      
      // Skip if not a .md or .txt file
      if (!href.endsWith('.md') && !href.endsWith('.txt')) continue;
      
      // Skip hidden files
      const filename = decodeURIComponent(href.split('/').pop() || '');
      if (filename.startsWith('.')) continue;
      
      const propstat = responseNode.getElementsByTagNameNS('DAV:', 'propstat')[0];
      const prop = propstat?.getElementsByTagNameNS('DAV:', 'prop')[0];
      
      const etag = prop?.getElementsByTagNameNS('DAV:', 'getetag')[0]?.textContent || '';
      const lastModified = prop?.getElementsByTagNameNS('DAV:', 'getlastmodified')[0]?.textContent || '';
      const modified = lastModified ? Math.floor(new Date(lastModified).getTime() / 1000) : 0;
      
      // Extract category from path and decode URL encoding
      const pathParts = href.split('/Notes/')[1]?.split('/');
      const category = pathParts && pathParts.length > 1 
        ? pathParts.slice(0, -1).map(part => decodeURIComponent(part)).join('/') 
        : '';
      
      // Create note with empty content - will be loaded on-demand
      const title = filename.replace(/\.(md|txt)$/, '');
      const note: Note = {
        id: category ? `${category}/${filename}` : filename,
        filename,
        path: category ? `${category}/${filename}` : filename,
        etag,
        readonly: false,
        content: '', // Empty - load on demand
        title,
        category,
        favorite: false,
        modified,
      };
      notes.push(note);
    }
    
    return notes;
  }

  async fetchNoteContentWebDAV(note: Note): Promise<Note> {
    const categoryPath = note.category ? `/${note.category}` : '';
    const filename = note.filename || String(note.id).split('/').pop() || 'note.md';
    const webdavPath = `/remote.php/dav/files/${this.username}/Notes${categoryPath}/${encodeURIComponent(filename)}`;
    const url = `${this.serverURL}${webdavPath}`;
    
    const response = await runtimeFetch(url, {
      headers: { 'Authorization': this.authHeader },
    });
    
    if (!response.ok) {
      throw new Error(`Failed to fetch note content: ${response.status}`);
    }
    
    const content = await response.text();
    return this.parseNoteFromContent(content, filename, note.category, note.etag, note.modified);
  }

  async createNoteWebDAV(title: string, content: string, category: string): Promise<Note> {
    const filename = `${title.replace(/[\/:\*?"<>|]/g, '').replace(/\s+/g, ' ').trim()}.md`;
    const categoryPath = category ? `/${category}` : '';
    const webdavPath = `/remote.php/dav/files/${this.username}/Notes${categoryPath}/${encodeURIComponent(filename)}`;
    const url = `${this.serverURL}${webdavPath}`;
    
    // Ensure category directory exists
    if (category) {
      try {
        const categoryUrl = `${this.serverURL}/remote.php/dav/files/${this.username}/Notes/${category}`;
        await runtimeFetch(categoryUrl, {
          method: 'MKCOL',
          headers: { 'Authorization': this.authHeader },
        });
      } catch (e) {
        // Directory might already exist
      }
    }
    
    const noteContent = `${title}\n${content}`;
    
    const response = await runtimeFetch(url, {
      method: 'PUT',
      headers: {
        'Authorization': this.authHeader,
        'Content-Type': 'text/plain',
      },
      body: noteContent,
    });

    if (!response.ok && response.status !== 201 && response.status !== 204) {
      throw new Error(`Failed to create note: ${response.status}`);
    }

    const etag = response.headers.get('etag') || '';
    const modified = Math.floor(Date.now() / 1000);
    
    return {
      id: category ? `${category}/${filename}` : filename,
      filename,
      path: category ? `${category}/${filename}` : filename,
      etag,
      readonly: false,
      content,
      title,
      category,
      favorite: false,
      modified,
    };
  }

  async updateNoteWebDAV(note: Note): Promise<Note> {
    // Extract new title from first line of content
    const firstLine = note.content.split('\n')[0].replace(/^#+\s*/, '').trim();
    const newTitle = firstLine || 'New Note';
    const newFilename = `${newTitle.replace(/[\/:\*?"<>|]/g, '').replace(/\s+/g, ' ').trim()}.md`;
    
    // Check if filename needs to change
    const needsRename = note.filename !== newFilename;
    
    if (needsRename) {
      // Rename the file first, then update content
      const renamedNote = await this.renameNoteWebDAV(note, newFilename);
      // Now update the content of the renamed file
      return this.updateNoteContentWebDAV(renamedNote);
    } else {
      // Just update content
      return this.updateNoteContentWebDAV(note);
    }
  }

  private async updateNoteContentWebDAV(note: Note): Promise<Note> {
    const categoryPath = note.category ? `/${note.category}` : '';
    const webdavPath = `/remote.php/dav/files/${this.username}/Notes${categoryPath}/${encodeURIComponent(note.filename!)}`;
    const url = `${this.serverURL}${webdavPath}`;
    
    const noteContent = this.formatNoteContent(note);
    
    const response = await runtimeFetch(url, {
      method: 'PUT',
      headers: {
        'Authorization': this.authHeader,
        'Content-Type': 'text/plain',
        'If-Match': note.etag, // Prevent overwriting if file changed
      },
      body: noteContent,
    });

    if (!response.ok && response.status !== 204) {
      if (response.status === 412) {
        throw new Error('Note was modified by another client. Please refresh.');
      }
      throw new Error(`Failed to update note: ${response.status}`);
    }

    const etag = response.headers.get('etag') || note.etag;
    
    return {
      ...note,
      etag,
      modified: Math.floor(Date.now() / 1000),
    };
  }

  private async renameNoteWebDAV(note: Note, newFilename: string): Promise<Note> {
    const categoryPath = note.category ? `/${note.category}` : '';
    const oldPath = `/remote.php/dav/files/${this.username}/Notes${categoryPath}/${encodeURIComponent(note.filename!)}`;
    const newPath = `/remote.php/dav/files/${this.username}/Notes${categoryPath}/${encodeURIComponent(newFilename)}`;
    
    const response = await runtimeFetch(`${this.serverURL}${oldPath}`, {
      method: 'MOVE',
      headers: {
        'Authorization': this.authHeader,
        'Destination': `${this.serverURL}${newPath}`,
      },
    });

    if (!response.ok && response.status !== 201 && response.status !== 204) {
      throw new Error(`Failed to rename note: ${response.status}`);
    }

    // Also rename attachment folder if it exists
    const oldNoteIdStr = String(note.id);
    const oldJustFilename = oldNoteIdStr.split('/').pop() || oldNoteIdStr;
    const oldFilenameWithoutExt = oldJustFilename.replace(/\.(md|txt)$/, '');
    const oldSanitizedNoteId = oldFilenameWithoutExt.replace(/[^a-zA-Z0-9_-]/g, '_');
    const oldAttachmentFolder = `.attachments.${oldSanitizedNoteId}`;
    
    const newFilenameWithoutExt = newFilename.replace(/\.(md|txt)$/, '');
    const newSanitizedNoteId = newFilenameWithoutExt.replace(/[^a-zA-Z0-9_-]/g, '_');
    const newAttachmentFolder = `.attachments.${newSanitizedNoteId}`;
    
    const oldAttachmentPath = `/remote.php/dav/files/${this.username}/Notes${categoryPath}/${oldAttachmentFolder}`;
    const newAttachmentPath = `/remote.php/dav/files/${this.username}/Notes${categoryPath}/${newAttachmentFolder}`;
    
    try {
      await runtimeFetch(`${this.serverURL}${oldAttachmentPath}`, {
        method: 'MOVE',
        headers: {
          'Authorization': this.authHeader,
          'Destination': `${this.serverURL}${newAttachmentPath}`,
        },
      });
    } catch (e) {
      // Attachment folder might not exist, that's ok
    }

    const newId = note.category ? `${note.category}/${newFilename}` : newFilename;
    
    return {
      ...note,
      id: newId,
      filename: newFilename,
      path: note.category ? `${note.category}/${newFilename}` : newFilename,
    };
  }

  async deleteNoteWebDAV(note: Note): Promise<void> {
    const categoryPath = note.category ? `/${note.category}` : '';
    const webdavPath = `/remote.php/dav/files/${this.username}/Notes${categoryPath}/${encodeURIComponent(note.filename!)}`;
    const url = `${this.serverURL}${webdavPath}`;
    
    const response = await runtimeFetch(url, {
      method: 'DELETE',
      headers: { 'Authorization': this.authHeader },
    });

    if (!response.ok && response.status !== 204) {
      throw new Error(`Failed to delete note: ${response.status}`);
    }
  }

  async moveNoteWebDAV(note: Note, newCategory: string): Promise<Note> {
    const oldCategoryPath = note.category ? `/${note.category}` : '';
    const newCategoryPath = newCategory ? `/${newCategory}` : '';
    const oldPath = `/remote.php/dav/files/${this.username}/Notes${oldCategoryPath}/${encodeURIComponent(note.filename!)}`;
    const newPath = `/remote.php/dav/files/${this.username}/Notes${newCategoryPath}/${encodeURIComponent(note.filename!)}`;
    
    // Ensure new category directory exists (including nested subdirectories)
    if (newCategory) {
      const parts = newCategory.split('/');
      let currentPath = '';
      
      for (const part of parts) {
        currentPath += (currentPath ? '/' : '') + part;
        try {
          const categoryUrl = `${this.serverURL}/remote.php/dav/files/${this.username}/Notes/${currentPath}`;
          await runtimeFetch(categoryUrl, {
            method: 'MKCOL',
            headers: { 'Authorization': this.authHeader },
          });
        } catch (e) {
          // Directory might already exist, continue
        }
      }
    }
    
    const response = await runtimeFetch(`${this.serverURL}${oldPath}`, {
      method: 'MOVE',
      headers: {
        'Authorization': this.authHeader,
        'Destination': `${this.serverURL}${newPath}`,
      },
    });

    if (!response.ok && response.status !== 201 && response.status !== 204) {
      throw new Error(`Failed to move note: ${response.status}`);
    }

    // Move attachment folder if it exists
    const noteIdStr = String(note.id);
    const justFilename = noteIdStr.split('/').pop() || noteIdStr;
    const filenameWithoutExt = justFilename.replace(/\.(md|txt)$/, '');
    const sanitizedNoteId = filenameWithoutExt.replace(/[^a-zA-Z0-9_-]/g, '_');
    const attachmentFolder = `.attachments.${sanitizedNoteId}`;
    
    const oldAttachmentPath = `/remote.php/dav/files/${this.username}/Notes${oldCategoryPath}/${attachmentFolder}`;
    const newAttachmentPath = `/remote.php/dav/files/${this.username}/Notes${newCategoryPath}/${attachmentFolder}`;
    
    console.log(`Attempting to move attachment folder:`);
    console.log(`  From: ${oldAttachmentPath}`);
    console.log(`  To: ${newAttachmentPath}`);
    
    try {
      const attachmentResponse = await runtimeFetch(`${this.serverURL}${oldAttachmentPath}`, {
        method: 'MOVE',
        headers: {
          'Authorization': this.authHeader,
          'Destination': `${this.serverURL}${newAttachmentPath}`,
        },
      });
      
      console.log(`Attachment folder MOVE response status: ${attachmentResponse.status}`);
      
      if (attachmentResponse.ok || attachmentResponse.status === 201 || attachmentResponse.status === 204) {
        console.log(`✓ Successfully moved attachment folder: ${attachmentFolder}`);
      } else {
        console.log(`✗ Failed to move attachment folder (status ${attachmentResponse.status})`);
      }
    } catch (e) {
      console.log(`✗ Error moving attachment folder:`, e);
    }

    return {
      ...note,
      category: newCategory,
      path: newCategory ? `${newCategory}/${note.filename}` : note.filename || '',
      id: newCategory ? `${newCategory}/${note.filename}` : (note.filename || ''),
    };
  }
}
