import { Component, inject, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { FormBuilder, ReactiveFormsModule, Validators, FormsModule } from '@angular/forms';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { InputTextModule } from 'primeng/inputtext';
import { PasswordModule } from 'primeng/password';
import { DropdownModule } from 'primeng/dropdown';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { ButtonModule } from 'primeng/button';
import { CardModule } from 'primeng/card';
import { DividerModule } from 'primeng/divider';
import { FileUploadModule } from 'primeng/fileupload';
import { ProgressBarModule } from 'primeng/progressbar';
import { DialogModule } from 'primeng/dialog';
import * as yaml from 'js-yaml';
import { environment } from '../../environments/environment';

@Component({
  selector: 'app-knock-page',
  standalone: true,
  imports: [
    CommonModule, RouterModule, ReactiveFormsModule, FormsModule,
    InputTextModule, PasswordModule, DropdownModule, CheckboxModule,
    InputTextareaModule, ButtonModule, CardModule, DividerModule,
    FileUploadModule, ProgressBarModule, DialogModule
  ],
  templateUrl: './knock-page.component.html',
  styleUrls: ['./knock-page.component.scss']
})
export class KnockPageComponent {
  private readonly http = inject(HttpClient);
  private readonly fb = inject(FormBuilder);

  @Input() enableFSA = false; // Включает File System Access API функциональность
  @Input() canUseFSA = false; // Доступна ли FSA версия

  // cardHeader = 'Port Knocker GUI';
  cardHeader = '';
  animatedTitle = 'Knock Knock Knock on the heaven\'s door ...';
  showInfoDialog = false;
  isAnimating = false;
  showResultDialog = false;

  executing = false;
  private timerId: any = null;
  private startTs = 0;
  elapsedMs = 0;
  lastRunTime: Date | null = null;
  wrongPass = false;
  selectedFileName: string | null = null;
  private fileHandle: any = null; // FileSystemFileHandle
  private isSyncing = false;
  result: string | null = null;
  error: string | null = null;

  form = this.fb.group({
    password: ['', Validators.required],
    mode: ['inline', Validators.required],
    targets: ['tcp:127.0.0.1:22'],
    delay: ['1s'],
    verbose: [true],
    waitConnection: [false],
    gateway: [''],
    configYAML: [''],
    serverFilePath: ['']
  });

  constructor() {
    // Загружаем сохраненное состояние из localStorage
    this.loadStateFromLocalStorage();
    
    // Запускаем анимацию заголовка
    this.startTitleAnimation();
    
    // Сбрасываем индикатор неверного пароля при изменении поля
    this.form.get('password')?.valueChanges.subscribe(() => {
      this.wrongPass = false;
    });

    // React on YAML text changes: extract path and sync to serverFilePath
    this.form.get('configYAML')?.valueChanges.subscribe((val) => {
      if (this.isSyncing) return;
      if (this.isInlineMode() || this.isYamlEncrypted()) return;
      try {
        const p = this.extractPathFromYaml(String(val ?? ''));
        const currentPath = this.form.value.serverFilePath || '';
        if (p && p !== currentPath) {
          this.isSyncing = true;
          this.form.patchValue({ serverFilePath: p });
          this.isSyncing = false;
        }
      } catch {}
    });

    // React on serverFilePath changes: update YAML path field
    this.form.get('serverFilePath')?.valueChanges.subscribe((newPath) => {
      if (this.isSyncing) return;
      this.onServerPathChange(newPath || '');
    });

    // Подписка на изменение режима для автоматического преобразования
    this.setupModeConversion();
    
    // Подписки на изменения полей для автосохранения в localStorage
    this.setupAutoSave();

    // File System Access API detection (для обратной совместимости)
    // Логика FSA теперь находится в отдельных компонентах
  }

  private authHeader(pass: string) {
    // Basic auth с пользователем "knocker"
    const token = btoa(`knocker:${pass}`);
    return { Authorization: `Basic ${token}` };
  }

  execute() {
    this.error = null;
    this.result = null;
    this.wrongPass = false;
    if (this.form.invalid) return;
    const v = this.form.value;
    const body: any = {
      targets: v.targets,
      delay: v.delay,
      verbose: v.verbose,
      waitConnection: v.waitConnection,
      gateway: v.gateway,
    };
    if (v.mode === 'yaml') {
      body.config_yaml = v.configYAML;
      delete body.targets;
      delete body.delay;
    }
    this.executing = true;
    this.startTimer();
    this.http.post('/api/v1/knock-actions/execute', body, {
      headers: this.authHeader(v.password || '')
    }).subscribe({
      next: () => {
        this.executing = false;
        this.stopTimer();
        this.lastRunTime = new Date();
        this.result = `Done in ${(this.elapsedMs/1000).toFixed(2)}s`;
        this.showResultDialog = true;
      },
      error: (e: HttpErrorResponse) => {
        this.executing = false;
        this.stopTimer();
        if (e.status === 401) {
          this.wrongPass = true;
        }
        this.error = (e.error?.error) || e.message;
        this.showResultDialog = true;
      }
    });
  }

  encrypt() {
    this.error = null;
    this.result = null;
    const v = this.form.value;
    if (this.isInlineMode() || this.isYamlEncrypted() || !v.password || this.wrongPass) {
      return;
    }
    
    // Проверяем есть ли path в YAML самом  
    const pathFromYaml = this.getPathFromYaml(v.configYAML || '');
    const serverFilePath = (this.form.value.serverFilePath || '').trim();
    
    let url: string;
    let body: any;
    
    if (pathFromYaml) {
      // Если path в YAML - используем /encrypt, сервер сам найдет path в YAML
      url = '/api/v1/knock-actions/encrypt';
      body = { yaml: v.configYAML };
    } else if (serverFilePath) {
      // Если path только в serverFilePath - используем /encrypt-file
      url = '/api/v1/knock-actions/encrypt-file';
      body = { path: serverFilePath };
    } else {
      // Нет пути - обычное шифрование содержимого
      url = '/api/v1/knock-actions/encrypt';
      body = { yaml: v.configYAML };
    }
    
    this.http.post(url, body, {
      headers: this.authHeader(v.password || '')
    }).subscribe({
      next: async (res: any) => {
        const encrypted: string = res.encrypted || '';
        
        // Всегда обновляем YAML поле зашифрованным содержимым
        this.form.patchValue({ configYAML: encrypted });
        
        if (pathFromYaml) {
          this.result = `Encrypted (YAML path: ${pathFromYaml})`;
          // НЕ сохраняем файл клиентом - сервер уже записал по path из YAML
        } else if (serverFilePath) {
          this.result = `Encrypted (server path: ${serverFilePath})`;
          // НЕ сохраняем файл клиентом - сервер записал по serverFilePath
        } else {
          this.result = 'Encrypted';
          // Только сохраняем в файл если НЕТ серверного пути
          await this.saveBackToFileIfPossible(encrypted, this.selectedFileName);
        }
      },
      error: (e: HttpErrorResponse) => this.error = (e.error && e.error.error) || e.message
    });
  }

  decrypt() {
    this.error = null;
    this.result = null;
    const v = this.form.value;
    if (this.isInlineMode() || !this.isYamlEncrypted() || !v.password || this.wrongPass) {
      return;
    }
    
    // Для зашифрованного YAML поле serverFilePath недоступно - используем только decrypt
    const url = '/api/v1/knock-actions/decrypt';
    const body = { encrypted: v.configYAML as string };
    
    this.http.post(url, body, {
      headers: this.authHeader(v.password || '')
    }).subscribe({
      next: async (res: any) => {
        const plain: string = res.yaml || '';
        this.form.patchValue({ configYAML: plain });
        this.result = 'Decrypted';
        
        // Извлекаем path из расшифрованного YAML и обновляем serverFilePath
        const pathFromDecrypted = this.getPathFromYaml(plain);
        if (pathFromDecrypted) {
          this.isSyncing = true;
          this.form.patchValue({ serverFilePath: pathFromDecrypted });
          this.isSyncing = false;
          this.result += ` (found path: ${pathFromDecrypted})`;
        }
        
        // НЕ делаем download - сервер уже обработал файл согласно path в YAML
      },
      error: (e: HttpErrorResponse) => this.error = (e.error && e.error.error) || e.message
    });
  }

  onFileSelected(event: Event) {
    const input = event.target as HTMLInputElement;
    if (!input.files || input.files.length === 0) return;
    const file = input.files[0];
    this.selectedFileName = file.name;
    const reader = new FileReader();
    reader.onload = () => {
      const text = String(reader.result || '');
      this.form.patchValue({ configYAML: text, mode: 'yaml' });
      // Sync path from YAML into serverFilePath
      const p = this.extractPathFromYaml(text);
      if (p) {
        this.isSyncing = true;
        this.form.patchValue({ serverFilePath: p });
        this.isSyncing = false;
      }
    };
    reader.readAsText(file);
    this.fileHandle = null; // обычная загрузка не даёт handle на запись
  }

  downloadYaml() {
    const yaml = this.form.value.configYAML || '';
    this.triggerDownload('config.yaml', yaml);
  }

  downloadResult() {
    const content = this.result || this.form.value.configYAML || '';
    const name = (content || '').startsWith('ENCRYPTED:') ? 'config.encrypted' : 'config.yaml';
    this.triggerDownload(name, content);
  }

  onFileUpload(event: any) {
    const files: File[] = event?.files || event?.currentFiles || [];
    if (!files.length) return;
    const file = files[0];
    this.selectedFileName = file.name;
    const reader = new FileReader();
    reader.onload = () => {
      const text = String(reader.result || '');
      this.form.patchValue({ configYAML: text, mode: 'yaml' });
      const p = this.extractPathFromYaml(text);
      if (!p) {
        return;
      }
      this.isSyncing = true;
      this.form.patchValue({ serverFilePath: p });
      this.isSyncing = false;
    };
    reader.readAsText(file);
    this.fileHandle = null;
  }

  async openFileWithWriteAccess() {
    try {
      const w: any = window as any;
      if (!w || typeof w.showOpenFilePicker !== 'function') {
        this.error = 'File System Access API is not supported by this browser.';
        return;
      }
      const [handle] = await w.showOpenFilePicker({
        types: [{ description: 'YAML/Encrypted', accept: { 'text/plain': ['.yaml', '.yml', '.encrypted', '.txt'] } }]
      });
      this.fileHandle = handle;
      const file = await handle.getFile();
      this.selectedFileName = file.name;
      const text = await file.text();
      this.form.patchValue({ configYAML: text, mode: 'yaml' });
      const p = this.extractPathFromYaml(text);
      if (p) {
        this.isSyncing = true;
        this.form.patchValue({ serverFilePath: p });
        this.isSyncing = false;
      }
      this.result = `Opened: ${file.name}`;
      this.error = null;
    } catch (e: any) {
      // user cancelled or error
    }
  }

  // Helpers for UI state
  isInlineMode(): boolean {
    return (this.form.value.mode === 'inline');
  }
  isYamlEncrypted(): boolean {
    const s = (this.form.value.configYAML || '').toString().trim();
    return s.startsWith('ENCRYPTED:');
  }

  private triggerDownload(filename: string, text: string) {
    const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url);
  }

  private async saveBackToFileIfPossible(content: string, filename: string | null) {
    try {
      const w: any = window as any;
      if (this.fileHandle && typeof this.fileHandle.createWritable === 'function') {
        const writable = await this.fileHandle.createWritable();
        await writable.write(content);
        await writable.close();
        return;
      }
      if (w && typeof w.showSaveFilePicker === 'function') {
        const handle = await w.showSaveFilePicker({
          suggestedName: filename || 'config.yaml',
          types: [{ description: 'YAML/Encrypted', accept: { 'text/plain': ['.yaml', '.yml', '.encrypted', '.txt'] } }]
        });
        const writable = await handle.createWritable();
        await writable.write(content);
        await writable.close();
        return;
      } else if (filename) {
        this.triggerDownload(filename, content);
      } else {
        this.triggerDownload('config.yaml', content);
      }
    } catch {
      if (filename) {
        this.triggerDownload(filename, content);
      } else {
        this.triggerDownload('config.yaml', content);
      }
    }
  }

  private startTimer() {
    this.elapsedMs = 0;
    this.startTs = Date.now();
    this.clearTimer();
    this.timerId = setInterval(() => {
      this.elapsedMs = Date.now() - this.startTs;
    }, 100);
  }

  private stopTimer() {
    if (this.startTs > 0) {
      this.elapsedMs = Date.now() - this.startTs;
    }
    this.clearTimer();
  }

  private clearTimer() {
    if (this.timerId) {
      clearInterval(this.timerId);
      this.timerId = null;
    }
  }

  // YAML path helpers
  private getPathFromYaml(text: string): string {
    return this.extractPathFromYaml(text);
  }

  private extractPathFromYaml(text: string): string {
    try {
      const doc: any = yaml.load(text);
      if (doc && typeof doc === 'object' && typeof doc.path === 'string') {
        return doc.path;
      }
    } catch {}
    return '';
  }

  onServerPathChange(newPath: string) {
    if (this.isInlineMode() || this.isYamlEncrypted()) return;
    environment.log('onServerPathChange', newPath);
    const current = String(this.form.value.configYAML || '');
    try {
      const doc: any = current.trim() ? yaml.load(current) : {};
      if (!doc || typeof doc !== 'object') return;
      (doc as any).path = newPath || '';
      this.isSyncing = true;
      this.form.patchValue({ configYAML: yaml.dump(doc, { lineWidth: 120 }) }, { emitEvent: true });
      this.isSyncing = false;
    } catch {}
  }

  // LocalStorage functionality
  private readonly STORAGE_KEY = 'knocker-ui-state';

  private loadStateFromLocalStorage() {
    try {
      const saved = localStorage.getItem(this.STORAGE_KEY);
      if (!saved) return;
      
      const state = JSON.parse(saved);
      environment.log('Loading saved state:', state);
      
      // Применяем сохраненные значения к форме
      const patchData: any = {};
      
      if (state.mode !== undefined) patchData.mode = state.mode;
      if (state.targets !== undefined) patchData.targets = state.targets;
      if (state.delay !== undefined) patchData.delay = state.delay;
      if (state.verbose !== undefined) patchData.verbose = state.verbose;
      if (state.waitConnection !== undefined) patchData.waitConnection = state.waitConnection;
      if (state.configYAML !== undefined) patchData.configYAML = state.configYAML;
      
      if (Object.keys(patchData).length > 0) {
        this.form.patchValue(patchData);
        
        // Если загружен YAML, извлекаем path и устанавливаем в serverFilePath
        if (state.configYAML) {
          const pathFromYaml = this.getPathFromYaml(state.configYAML);
          if (pathFromYaml) {
            this.isSyncing = true;
            this.form.patchValue({ serverFilePath: pathFromYaml });
            this.isSyncing = false;
            environment.log('Extracted path from loaded YAML:', pathFromYaml);
          }
        }
      }
    } catch (e) {
      console.warn('Failed to load state from localStorage:', e);
    }
  }

  private saveStateToLocalStorage() {
    try {
      const formValue = this.form.value;
      const state = {
        mode: formValue.mode,
        targets: formValue.targets,
        delay: formValue.delay,
        verbose: formValue.verbose,
        waitConnection: formValue.waitConnection,
        configYAML: formValue.configYAML
      };
      
      localStorage.setItem(this.STORAGE_KEY, JSON.stringify(state));
      environment.log('State saved to localStorage:', state);
    } catch (e) {
      console.warn('Failed to save state to localStorage:', e);
    }
  }

  private setupAutoSave() {
    // Подписываемся на изменения нужных полей
    const fieldsToWatch = ['mode', 'targets', 'delay', 'verbose', 'waitConnection', 'configYAML'];
    
    fieldsToWatch.forEach(fieldName => {
      this.form.get(fieldName)?.valueChanges.subscribe(() => {
        // Небольшая задержка, чтобы не сохранять на каждое нажатие клавиши
        setTimeout(() => this.saveStateToLocalStorage(), 300);
      });
    });
  }

  // Автоматическое преобразование между режимами
  private setupModeConversion() {
    let previousMode = this.form.value.mode;
    
    this.form.get('mode')?.valueChanges.subscribe((newMode) => {
      if (this.isSyncing) return;
      
      environment.log(`Mode changed from ${previousMode} to ${newMode}`);
      
      if (previousMode === 'inline' && newMode === 'yaml') {
        this.handleModeChangeToYaml();
      } else if (previousMode === 'yaml' && newMode === 'inline') {
        this.handleModeChangeToInline();
      }
      
      previousMode = newMode;
    });
  }

  private handleModeChangeToYaml() {
    try {
      // Проверяем есть ли сохраненный YAML в localStorage
      const savedYaml = this.getSavedConfigYAML();
      
      if (savedYaml?.trim()) {
        // Используем сохраненный YAML
        environment.log('Using saved YAML from localStorage');
        this.isSyncing = true;
        this.form.patchValue({ configYAML: savedYaml });
        this.isSyncing = false;
      } else {
        // Конвертируем из inline
        environment.log('Converting inline to YAML');
        this.convertInlineToYaml();
      }
      
      // После установки YAML (из localStorage или конвертации) извлекаем path
      setTimeout(() => this.extractAndSetServerPath(), 100);
      
    } catch (e) {
      console.warn('Failed to handle mode change to YAML:', e);
    }
  }

  private handleModeChangeToInline() {
    try {
      // Проверяем есть ли сохраненные inline значения в localStorage
      const savedTargets = this.getSavedTargets();
      
      if (savedTargets && savedTargets.trim()) {
        // Используем сохраненные inline значения
        environment.log('Using saved inline targets from localStorage');
        this.isSyncing = true;
        this.form.patchValue({ targets: savedTargets });
        this.isSyncing = false;
      } else {
        // Конвертируем из YAML
        environment.log('Converting YAML to inline');
        this.convertYamlToInline();
      }
      
    } catch (e) {
      console.warn('Failed to handle mode change to inline:', e);
    }
  }

  private getSavedConfigYAML(): string | null {
    try {
      const saved = localStorage.getItem(this.STORAGE_KEY);
      if (!saved) return null;
      const state = JSON.parse(saved);
      return state.configYAML || null;
    } catch {
      return null;
    }
  }

  private getSavedTargets(): string | null {
    try {
      const saved = localStorage.getItem(this.STORAGE_KEY);
      if (!saved) return null;
      const state = JSON.parse(saved);
      return state.targets || null;
    } catch {
      return null;
    }
  }

  private extractAndSetServerPath() {
    try {
      const yamlContent = this.form.value.configYAML || '';
      if (!yamlContent.trim()) return;
      
      const config: any = yaml.load(yamlContent);
      if (config && typeof config === 'object' && config.path) {
        environment.log('Extracted path from YAML:', config.path);
        this.isSyncing = true;
        this.form.patchValue({ serverFilePath: config.path });
        this.isSyncing = false;
      }
    } catch (e) {
      console.warn('Failed to extract path from YAML:', e);
    }
  }

  private convertInlineToYaml() {
    try {
      const formValue = this.form.value;
      const targets = (formValue.targets || '').split(';').filter(t => t.trim());
      
      // Создаем YAML конфигурацию из inline параметров
      const config: any = {
        targets: targets.map(target => {
          const [protocol, address] = target.trim().split(':');
          const [host, port] = address ? address.split(':') : ['', ''];
          
          return {
            protocol: protocol || 'tcp',
            host: host || '127.0.0.1',
            ports: [parseInt(port) || 22],
            wait_connection: formValue.waitConnection || false
          };
        }),
        delay: formValue.delay || '1s'
      };
      
      const yamlString = yaml.dump(config, { lineWidth: 120 });
      
      this.isSyncing = true;
      this.form.patchValue({ configYAML: yamlString });
      this.isSyncing = false;
      
      environment.log('Converted inline to YAML:', config);
    } catch (e) {
      console.warn('Failed to convert inline to YAML:', e);
    }
  }

  private convertYamlToInline() {
    try {
      const yamlContent = this.form.value.configYAML || '';
      if (!yamlContent.trim()) {
        // Если YAML пустой, используем значения по умолчанию
        this.isSyncing = true;
        this.form.patchValue({ 
          targets: 'tcp:127.0.0.1:22',
          delay: '1s',
          waitConnection: false
        });
        this.isSyncing = false;
        return;
      }
      
      const config: any = yaml.load(yamlContent);
      if (!config || !config.targets || !Array.isArray(config.targets)) {
        console.warn('Invalid YAML structure for conversion');
        return;
      }
      
      // Конвертируем targets в строку - создаем отдельную строку для каждого порта
      const targetStrings: string[] = [];
      config.targets.forEach((target: any) => {
        const protocol = target.protocol || 'tcp';
        const host = target.host || '127.0.0.1';
        // Поддерживаем как ports (массив), так и port (единственное число) для обратной совместимости
        const ports = target.ports || [target.port] || [22];
        
        if (Array.isArray(ports)) {
          // Создаем отдельную строку для каждого порта
          ports.forEach(port => {
            targetStrings.push(`${protocol}:${host}:${port}`);
          });
        } else {
          targetStrings.push(`${protocol}:${host}:${ports}`);
        }
      });
      
      const targetsString = targetStrings.join(';');
      const delay = config.delay || '1s';
      
      // Берем wait_connection из первой цели (если есть)
      const waitConnection = config.targets[0]?.wait_connection || false;
      
      this.isSyncing = true;
      this.form.patchValue({ 
        targets: targetsString,
        delay: delay,
        waitConnection: waitConnection
      });
      this.isSyncing = false;
      
      environment.log('Converted YAML to inline:', { targets: targetsString, delay, waitConnection });
    } catch (e) {
      console.warn('Failed to convert YAML to inline:', e);
    }
  }

  // Публичный метод для очистки сохраненного состояния (опционально)
  clearSavedState() {
    try {
      localStorage.removeItem(this.STORAGE_KEY);
      environment.log('Saved state cleared from localStorage');
      
      // Сбрасываем форму к значениям по умолчанию
      this.form.patchValue({
        mode: 'inline',
        targets: 'tcp:127.0.0.1:22',
        delay: '1s',
        verbose: true,
        waitConnection: false,
        configYAML: ''
      });
    } catch (e) {
      console.warn('Failed to clear saved state:', e);
    }
  }

  // Анимация заголовка
  private startTitleAnimation() {
    if (this.isAnimating) return;
    this.isAnimating = true;
    
    // Первая анимация: по буквам
    this.animateByLetters();
  }

  private animateByLetters() {
    let currentIndex = 0;
    const letters = this.animatedTitle.split('');
    
    const interval = setInterval(() => {
      if (currentIndex < letters.length) {
        this.cardHeader = letters.slice(0, currentIndex + 1).join('');
        currentIndex++;
      } else {
        clearInterval(interval);
        // Ждем 2 секунды и начинаем исчезать
        setTimeout(() => {
          this.fadeOutTitle();
        }, 2000);
      }
    }, 100); // 100ms между буквами
  }

  private fadeOutTitle() {
    let opacity = 1;
    const fadeInterval = setInterval(() => {
      opacity -= 0.1;
      if (opacity <= 0) {
        clearInterval(fadeInterval);
        this.cardHeader = '';
        // Ждем 1 секунду и начинаем анимацию по словам
        setTimeout(() => {
          this.animateByWords();
        }, 1000);
      }
    }, 50);
  }

  private animateByWords() {
    const words = this.animatedTitle.split(' ');
    let currentIndex = 0;
    
    const interval = setInterval(() => {
      if (currentIndex < words.length) {
        this.cardHeader = words.slice(0, currentIndex + 1).join(' ');
        currentIndex++;
      } else {
        clearInterval(interval);
        this.isAnimating = false;
      }
    }, 300); // 300ms между словами
  }
}


