import {
  ImageConfiguration,
  ImagesIncludeId,
} from "./../../interfaces/image.interface";
import { CustomAutocompleteComponentApi } from "./../inputs/custom-autocomplete-api/custom-autocomplete-api.component";
import {
  Component,
  Output,
  EventEmitter,
  Inject,
  TemplateRef,
  OnInit,
} from "@angular/core";
import { CommonModule } from "@angular/common";
import {
  FormGroup,
  FormBuilder,
  ReactiveFormsModule,
  Validators,
  AbstractControl,
  ValidationErrors,
  FormControl,
} from "@angular/forms";
import {
  MatDialogRef,
  MAT_DIALOG_DATA,
  MatDialogModule,
  MatDialog,
} from "@angular/material/dialog";
import { ConfirmDialogComponent } from "../confirm-dialog/confirm-dialog.component";
import { MatAutocompleteModule } from "@angular/material/autocomplete";
import { MatInputModule } from "@angular/material/input";
import { MatFormFieldModule } from "@angular/material/form-field";
import { CustomInputComponent } from "../inputs/custom-input/custom-input.component";
import { CustomAutocompleteComponent } from "../inputs/custom-autocomplete/custom-autocomplete.component";
import {
  catchError,
  forkJoin,
  map,
  Observable,
  of,
  shareReplay,
  switchMap,
  take,
  tap,
} from "rxjs";
import {
  ImageApiResponse,
  ImagesIncludeIdExtended,
  ImageUploaderComponent,
} from "../image-uploader/image-uploader.component";
import { DataUrlFile } from "../../interfaces/image.interface";
import { ImageUploadService } from "../image-uploader/image-uploader.service";
import { HttpClient } from "@angular/common/http";
import { CheckboxListComponent } from "../checkbox-list/checkbox-list.component";
import { CustomTabsComponent } from "../custom-tabs/custom-tabs.component";

export interface FormField {
  key: string;
  label: string;
  type:
    | "text"
    | "number"
    | "select"
    | "date"
    | "checkbox"
    | "textarea"
    | "select-api"
    | "send-image";
  required?: boolean;
  options?: { value: any; label: string }[];
  returnObjectSelected?: boolean;
  validators?: any[];
  defaultValue?: any;
  mask?: string;
  placeholder?: string;
  uppercase?: boolean;
  disabled?: boolean;
  imageConfiguration?: ImageConfiguration;
  fetchApi?: (search: string) => Observable<{ value: any; label: string }[]>;
  checkBoxList?: { name: string; value: boolean }[];
  onValueChange?: (value: any, formGroup?: any) => void;
}

export interface FormTab {
  id: string;
  label: string;
  fields?: string[];
  template?: TemplateRef<{ tab: FormTab; form: FormGroup }>;
}

export type FormTabWithContent =
  | { id: string; label: string; fields: FormField[]; template?: never }
  | { id: string; label: string; template: TemplateRef<any>; fields?: never };

export interface FormConfig {
  title: string;
  fields: FormField[];
  submitLabel?: string;
  cancelLabel?: string;
  uppercase?: boolean;
  tabs?: FormTabWithContent[];
}

@Component({
  selector: "app-generic-form",
  standalone: true,
  imports: [
    CommonModule,
    ReactiveFormsModule,
    MatDialogModule,
    MatAutocompleteModule,
    MatInputModule,
    MatFormFieldModule,
    CustomInputComponent,
    CustomAutocompleteComponent,
    CustomAutocompleteComponentApi,
    ImageUploaderComponent,
    CheckboxListComponent,
    CustomTabsComponent,
  ],
  templateUrl: "./generic-form.component.html",
  styleUrls: ["./generic-form.component.scss"],
})
export class GenericFormComponent implements OnInit {
  @Output() formSubmit = new EventEmitter<any>();
  config!: FormConfig;
  initialData?: any;
  form: FormGroup;
  isFormDirty = false;
  isSubmitting = false;
  imageFieldData: {
    [key: string]: { images: ImagesIncludeId[]; files: File[] };
  } = {};
  imagePreviewsMap: { [key: string]: Observable<ImagesIncludeId[]> } = {};

  constructor(
    private fb: FormBuilder,
    private dialogRef: MatDialogRef<GenericFormComponent>,
    private imageUploadService: ImageUploadService,
    private http: HttpClient,
    private dialog: MatDialog,
    @Inject(MAT_DIALOG_DATA) data: { config: FormConfig; initialData?: any }
  ) {
    this.config = data.config;
    this.initialData = data.initialData;
    this.form = this.fb.group({});
    this.initForm();

    this.dialogRef.disableClose = true;

    this.form.valueChanges.subscribe(() => {
      this.isFormDirty = true;
    });

    this.setupFieldValueChangeListeners();

    this.dialogRef.backdropClick().subscribe((event: MouseEvent) => {
      const modalContent = document.querySelector(".custom-dialog-container");
      if (!modalContent?.contains(event.target as Node)) {
        this.confirmClose();
      }
    });
  }

  ngOnInit() {
    this.initializeImagePreviews();
  }

  private initializeImagePreviews() {
    const allFields = this.getAllFields();

    allFields
      .filter((field) => field.type === "send-image")
      .forEach((field) => {
        const existingIds = field.imageConfiguration?.existingImages || [];

        this.imagePreviewsMap[field.key] = this.getImagePreviewsWithBase64(
          existingIds
        ).pipe(
          catchError(() => of([])),
          tap((images) => {
            this.initializeImageData(field.key, images);
          })
        );
      });
  }

  // Função validadora personalizada para options
  private createOptionValidator(options: { value: any; label: string }[]) {
    return (control: AbstractControl): ValidationErrors | null => {
      if (!control.value) return null;

      const valueToCheck =
        typeof control.value === "object" ? control.value.value : control.value;

      const isValid = options.some((option) => option.value === valueToCheck);
      return isValid ? null : { invalidOption: true };
    };
  }

  private getAllFields(): FormField[] {
    const mainFields = this.config.fields || [];
    const tabFields =
      this.config.tabs?.flatMap((tab) => tab.fields || []) || [];
    return [...mainFields, ...tabFields];
  }

  private initForm() {
    const group: any = {};
    const allFields = this.getAllFields();

    allFields.forEach((field) => {
      const value = this.initialData?.[field.key] ?? field.defaultValue ?? "";

      const validators = [];

      if (field.required) {
        validators.push(Validators.required);
      }

      if (field.type === "select" && field.options) {
        validators.push(this.createOptionValidator(field.options));
        if (typeof value === "string" && field.options) {
          const foundOption = field.options.find((opt) => opt.value === value);
          if (foundOption) {
            group[field.key] = field.returnObjectSelected
              ? this.fb.control(foundOption, validators)
              : this.fb.control(foundOption.value, validators);
            return;
          }
        }
      }

      if (field.validators && field.validators.length > 0) {
        validators.push(...field.validators);
      }

      if (field.type === "checkbox") {
        let initialValue = field.checkBoxList || [];
        if (this.initialData?.[field.key]) {
          initialValue = this.initialData[field.key];
        } else if (field.defaultValue) {
          initialValue = field.defaultValue;
        }

        if (!Array.isArray(initialValue)) {
          initialValue =
            field.checkBoxList?.map((item) => ({
              name: item.name,
              value: false,
            })) || [];
        }

        if (field.disabled === true) {
          group[field.key] = this.fb.control({ value: initialValue, disabled: true }, validators);
        } else {
          group[field.key] = this.fb.control(initialValue, validators);
        }
      } else {
        let control;
        if (field.disabled === true) {
          control = this.fb.control({ value: value, disabled: true }, validators);
        } else {
          control = this.fb.control(value, validators);
        }

        if (field.uppercase && !field.disabled) {
          control.valueChanges.subscribe((val) => {
            if (val && typeof val === "string" && val !== val.toUpperCase()) {
              control.setValue(val.toUpperCase(), { emitEvent: false });
            }
          });
        }

        group[field.key] = control;
      }
    });

    this.form = this.fb.group(group);
  }

  onSubmit() {
    if (!this.form.valid) {
      this.form.markAllAsTouched();
      return;
    }

    const formData = this.form.value;
    const convertedData: { [key: string]: any } = {};
    const imageUploadTasks: Observable<any>[] = [];
    const allFields = this.getAllFields();

    allFields.forEach((field) => {
      if (field.type === "select" && field.returnObjectSelected) {
        const fieldValue = formData[field.key];
        if (typeof fieldValue === "string") {
          const foundOption = field.options?.find(
            (opt) => opt.value === fieldValue
          );
          convertedData[field.key] = foundOption || { value: fieldValue };
        } else {
          convertedData[field.key] = fieldValue;
        }
        return;
      }

      switch (field.type) {
        case "number":
          convertedData[field.key] = formData[field.key]
            ? Number(formData[field.key])
            : null;
          break;
        case "date":
          convertedData[field.key] = formData[field.key]
            ? new Date(formData[field.key])
            : null;
          break;
        case "checkbox":
          convertedData[field.key] = formData[field.key];
          break;
        case "send-image":
          const task = this.getImageIds(this.imageFieldData[field.key]).pipe(
            tap((ids) => {
              convertedData[field.key] = ids;
            })
          );
          imageUploadTasks.push(task);
          break;
        default:
          convertedData[field.key] = formData[field.key];
      }
    });

    this.isSubmitting = true;
    forkJoin([
      ...(imageUploadTasks.length ? imageUploadTasks : [of(undefined)]),
    ]).subscribe(() => {
      this.formSubmit.emit({
        data: convertedData,
        closeDialog: () => {
          this.dialogRef.close();
        },
      });
    });
  }

  close() {
    this.dialogRef.close();
  }

  getFieldType(fieldKey: FormField): string {
    return fieldKey.type;
  }

  getFieldConfig(fieldKey: FormField): FormField {
    return fieldKey;
  }

  getSafeFetchApi(
    fieldKey: FormField
  ): (search: string) => Observable<Array<{ value: any; label: string }>> {
    const field = fieldKey;
    return field.fetchApi || this.defaultFetchApi;
  }

  defaultFetchApi = (search: string) =>
    of<Array<{ value: any; label: string }>>([]);

  getInitialValue(field: FormField): any {
    const value = this.initialData?.[field.key] ?? field.defaultValue ?? "";

    if (field.type === "select" && field.options && typeof value === "string") {
      return field.options.find((opt) => opt.value === value)?.value || value;
    }

    return value;
  }

  getCheckboxControl(key: string): FormControl {
    return (this.form.get(key) as FormControl) || new FormControl([]);
  }

  getImageIds(
    imageData: { images?: ImagesIncludeIdExtended[]; files?: File[] } = {}
  ): Observable<number[]> {
    if (!imageData || (!imageData.images && !imageData.files)) {
      return of([]);
    }
    const safeData = {
      images:
        imageData.images?.filter(
          (img) => img !== null && img.id !== undefined
        ) || [],
      files: imageData.files || [],
    };

    const existingIds = safeData.images
      .filter(
        (img): img is { id: number; image: any } =>
          typeof img.id === "number" && !isNaN(img.id)
      )
      .map((img) => img.id);

    const filesToUpload = [
      ...safeData.files,
      ...safeData.images
        .filter((img) => !img.id && img.image && !img.file)
        .map((img) => this.convertToFile(img.image)),
    ].filter(Boolean) as File[];

    if (filesToUpload.length === 0) {
      return of(existingIds);
    }

    const uploadObservables = filesToUpload.map((file) =>
      this.uploadFile(file).pipe(
        map((id) => Number(id)),
        catchError((error) => {
          console.error("Falha no upload:", error);
          return of(null);
        }),
        shareReplay(1)
      )
    );

    return forkJoin(uploadObservables).pipe(
      map((newIds) => {
        const validNewIds = newIds.filter((id): id is number => id !== null);
        return [...existingIds, ...validNewIds];
      })
    );
  }

  private uploadFile(file: File): Observable<string> {
    const uploadFile =
      file.name === "uploaded-image"
        ? new File(
            [file],
            `image-${Date.now()}${this.getFileExtension(file.type)}`,
            { type: file.type }
          )
        : file;

    return this.imageUploadService.uploadImageUrl(uploadFile.name).pipe(
      switchMap((dataUrlFile: DataUrlFile) =>
        this.http
          .put(dataUrlFile.data.uploadUrl, uploadFile)
          .pipe(
            switchMap(() =>
              this.imageUploadService.uploadImageConfirm(
                dataUrlFile.data.storageKey
              )
            )
          )
      ),
      map((response: any) => response.data.id.toString()),
      take(1)
    );
  }

  private convertToFile(image: string | File): File | null {
    if (image instanceof File) {
      return image;
    }

    try {
      const mimeType = image.match(/data:(.*?);/)?.[1] || "image/jpeg";
      const fileName = this.extractFileNameFromBase64(image) || "custom-name";

      const byteString = atob(image.split(",")[1]);
      const ab = new ArrayBuffer(byteString.length);
      const ia = new Uint8Array(ab);

      for (let i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
      }

      return new File([new Blob([ab], { type: mimeType })], fileName, {
        type: mimeType,
      });
    } catch (error) {
      console.error("Conversão falhou:", error);
      return null;
    }
  }

  private extractFileNameFromBase64(base64: string): string | null {
    const metadataMatch = base64.match(/filename=(.*?);/);
    if (metadataMatch && metadataMatch[1]) {
      return metadataMatch[1];
    }
    return null;
  }

  private getFileExtension(mimeType: string): string {
    const extensions: { [key: string]: string } = {
      "image/jpeg": ".jpg",
      "image/png": ".png",
      "image/gif": ".gif",
      "image/webp": ".webp",
    };
    return extensions[mimeType] || "";
  }

  confirmClose() {
    if (this.isFormDirty) {
      const confirmDialog = this.dialog.open(ConfirmDialogComponent, {
        width: "400px",
        data: {
          message: "Existem alterações não salvas. Deseja realmente fechar?",
        },
      });

      confirmDialog.afterClosed().subscribe((result) => {
        if (result) {
          this.dialogRef.close();
        }
      });
    } else {
      this.dialogRef.close();
    }
  }

  getFormControl(key: string): FormControl {
    return this.form.get(key) as FormControl;
  }

  onSelectionChanged(selected: { name: string; value: boolean }[]) {
    //changed CheckBox
  }

  handleImageChange(fieldKey: string, files: File[]) {
    const existingImages = this.imageFieldData[fieldKey]?.images || [];

    this.imageFieldData[fieldKey] = {
      images: existingImages,
      files: files,
    };
  }

  initializeImageData(fieldKey: string, initialImages: ImagesIncludeId[]) {
    if (!this.imageFieldData[fieldKey]) {
      this.imageFieldData[fieldKey] = {
        images: initialImages.filter((img) => img.id),
        files: [],
      };
    }
  }

  handlePreviewImagesChange(fieldKey: string, images: ImagesIncludeId[]) {
    const currentFiles = this.imageFieldData[fieldKey]?.files || [];

    this.imageFieldData[fieldKey] = {
      images: images,
      files: currentFiles,
    };
  }

  handleImageError(msg: string) {
    console.warn("Erro ao enviar imagem:", msg);
  }

  getImagePreviewsWithBase64(
    imageIds: number[]
  ): Observable<ImagesIncludeId[]> {
    if (!imageIds?.length) return of([]);

    return forkJoin(imageIds.map((id) => this.loadSingleImage(id)));
  }

  private loadSingleImage(id: number): Observable<ImagesIncludeId> {
    return this.imageUploadService.getImageById(id).pipe(
      switchMap((response) =>
        this.convertToImageIncludeId(id, response.data.downloadUrl)
      ),
      catchError((error) => {
        console.error(`Error loading image ${id}:`, error);
        return of(this.createDefaultImage(id));
      })
    );
  }

  private convertToImageIncludeId(
    id: number,
    url: string
  ): Observable<ImagesIncludeId> {
    return this.convertImageUrlToBase64(url).pipe(
      map((base64) => ({ id, image: base64 }))
    );
  }

  private createDefaultImage(id: number): ImagesIncludeId {
    return { id, image: "" };
  }

  private convertImageUrlToBase64(imageUrl: string): Observable<string> {
    return new Observable((observer) => {
      const img = new Image();
      img.crossOrigin = "Anonymous";
      img.src = imageUrl;

      img.onload = () => {
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");
        canvas.width = img.width;
        canvas.height = img.height;
        ctx?.drawImage(img, 0, 0);

        try {
          const dataUrl = canvas.toDataURL("image/png");
          observer.next(dataUrl);
          observer.complete();
        } catch (e) {
          observer.error(e);
        }
      };

      img.onerror = (err) => {
        observer.error(err);
      };
    });
  }

  restrictToNumbers(event: KeyboardEvent, fieldType: string) {
    if (fieldType === "number") {
      const charCode = event.charCode;
      if (charCode < 48 || charCode > 57) {
        event.preventDefault();
      }
    }
  }

  private setupFieldValueChangeListeners() {
    const allFields = this.getAllFields();
    allFields.forEach((field) => {
      if (field.onValueChange) {
        this.form.get(field.key)?.valueChanges.subscribe((value) => {
          if (field.onValueChange) {
            field.onValueChange(value, this.form);
          }
        });
      }
    });
  }
}
