import {Component, OnInit} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {DecimalPipe, JsonPipe, NgForOf, NgIf} from "@angular/common";
import {HttpClient} from "@angular/common/http";
import {environment} from "../../environments/environment";
import {countryCodeToFlag, formatDuration} from "../format";
import {OsuGradeComponent} from "../../corelib/components/osu-grade/osu-grade.component";
import {
  FieldType,
  Operator,
  Query,
  QueryBuilderComponent
} from "../../corelib/components/query-builder/query-builder.component";
import {RouterLink} from "@angular/router";
import {CalculatePageRangePipe} from "../../corelib/calculate-page-range.pipe";
import {DownloadFilesService} from "../../corelib/service/download-files.service";
import {CuteLoadingComponent} from "../../corelib/components/cute-loading/cute-loading.component";
import {Title} from "@angular/platform-browser";

export interface SchemaField {
  name: string;
  shortName: string;
  category: 'user' | 'score' | 'beatmap' | 'metrics';
  type: 'number' | 'string' | 'flag' | 'grade' | 'boolean' | 'datetime' | 'playtime';
  validOperators: Operator[];
  active: boolean;
  description: string;
}

interface SchemaResponse {
  fields: SchemaField[];
}

interface SearchResponse {
  scores: any[];
  pagination: SearchPagination;
}

interface SearchPagination {
  currentPage: number;
  pageSize: number;
  totalResults: number;
  totalPages: number;
}

interface Sorting {
  field: string;
  order: 'ASC' | 'DESC';
}

@Component({
  selector: 'app-search',
  standalone: true,
  imports: [
    ReactiveFormsModule,
    NgForOf,
    FormsModule,
    JsonPipe,
    NgIf,
    DecimalPipe,
    OsuGradeComponent,
    QueryBuilderComponent,
    RouterLink,
    CalculatePageRangePipe,
    CuteLoadingComponent
  ],
  templateUrl: './search.component.html',
  styleUrl: './search.component.css'
})
export class SearchComponent implements OnInit {

  constructor(private httpClient: HttpClient,
              private title: Title,
              public downloadFilesService: DownloadFilesService) { }

  currentSchemaVersion = 2

  isError = false;
  isLoading = false;
  isLoadingSchema = true;
  response: SearchResponse | null = null;

  fields: SchemaField[] = [];

  sortingOrder: Sorting | null = null;
  queries: Query[] | null = null;

  ngOnInit(): void {
    this.title.setTitle("/k/ - advanced search");
    this.isLoadingSchema = true;
    this.httpClient.get<SchemaResponse>(`${environment.apiUrl}/search/schema`,).subscribe({
      next: (response) => {
        this.fields = response.fields;
        this.fields.forEach(field => {
          field.validOperators = this.getOperators(field.type);
        })
        this.loadPreviousFromLocalStorage();
        this.isLoadingSchema = false;
      },
      error: () => {
        alert('Error fetching schema');
      }
    });
  }

  getOperators(fieldType: FieldType | undefined): Operator[] {
    switch (fieldType) {
      case 'number':
        return ['=', '>', '<', '>=', '<=', '!=']
          .map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
      case 'string':
        return ['=', 'contains', 'like']
          .map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
      case 'boolean':
        return ['=', '!=']
          .map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'boolean'}) as Operator);
      case 'flag':
        return ['=', '!=']
          .map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'flag'}) as Operator);
      case 'grade':
        return ['=', '!=']
          .map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'grade'}) as Operator);
      case 'datetime':
        return ['before', 'after']
          .map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'datetime'}) as Operator);
      case 'playtime':
        return ['>', '<', '>=', '<=']
          .map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
      default:
        return [];
    }
  }

  private loadPreviousFromLocalStorage(): void {
    const storedQueries = localStorage.getItem('search_settings');
    let parsedQueries = storedQueries ? JSON.parse(storedQueries) : null;

    if (parsedQueries && this.verifySchema(parsedQueries)) {
      this.queries = parsedQueries.queries;
      this.sortingOrder = parsedQueries.sortingOrder;
      this.fields.forEach(field => {
        field.active = parsedQueries.columns[field.name] ?? field.active;
      });
    } else {
      localStorage.removeItem('search_settings');
      this.queries = [];
      this.sortingOrder = {
        field: 'user_id',
        order: 'ASC'
      };
    }
  }

  deselectEntireFieldCategory(categoryName: string): void {
    this.fields.forEach(field => {
      if (field.category === categoryName) {
        field.active = false;
      }
    });
    this.saveSettingsToLocalStorage();
  }

  selectEntireFieldCategory(categoryName: string): void {
    this.fields.forEach(field => {
      if (field.category === categoryName) {
        field.active = true;
      }
    });
    this.saveSettingsToLocalStorage();
  }

  private serializeSettings() {
    return {
      queries: this.queries,
      sortingOrder: this.sortingOrder,
      columns: this.getColumnSettings(),
      schemaVersion: this.currentSchemaVersion
    };
  }

  private getColumnSettings() {
    return this.fields.reduce<{ [key: string]: boolean }>((acc, field) => {
      acc[field.name] = field.active;
      return acc;
    }, {});
  }

  onSortingFieldChange(event: Event): void {
    const selectElement = event.target as HTMLSelectElement;
    if(!selectElement) {
      return;
    }

    if(this.sortingOrder === null) {
      this.sortingOrder = {
        field: 'user_id',
        order: 'ASC'
      };
    }

    this.sortingOrder.field = selectElement.value;
  }

  saveSettingsToLocalStorage(): void {
    const settings = this.serializeSettings();
    localStorage.setItem('search_settings', JSON.stringify(settings));
  }

  exportSettings(): void {
    const settings = this.serializeSettings();
    this.downloadFilesService.downloadJSON(settings as any, 'nise-settings');
  }

  importSettings(json: any): void {
    if (this.verifySchema(json)) {
      this.queries = json.queries;
      this.sortingOrder = json.sortingOrder;
      this.fields.forEach(field => {
        if (json.columns.hasOwnProperty(field.name)) {
          field.active = json.columns[field.name];
        }
      });
    } else {
      alert('Invalid settings file.');
    }
  }

  getColumns(): string[] {
    return this.fields.map(field => field.name);
  }

  uploadSettingsFile(event: any): void {
    const file = event.target.files[0];
    if (file) {
      const fileReader = new FileReader();
      fileReader.onload = (e) => {
        try {
          const json = JSON.parse(fileReader.result as string);
          this.importSettings(json);
        } catch (error) {
          console.error('Error parsing JSON', error);
        }
      };
      fileReader.readAsText(file);
    }
  }

  verifySchema(json: any): boolean {
    if(!('schemaVersion' in json) || json.schemaVersion < this.currentSchemaVersion) {
      return false;
    }
    return 'queries' in json && 'sortingOrder' in json && 'columns' in json;
  }

  search(pageNumber: number = 1): void {
    this.isLoading = true;
    this.isError = false;
    this.response = null;

    const body = {
      queries: this.queries,
      sorting: this.sortingOrder,
      page: pageNumber
    }
    this.httpClient.post<SearchResponse>(`${environment.apiUrl}/search`, body)
      .subscribe({
        next: (response) => {
          this.response = response;
          this.isLoading = false;
        },
        error: () => {
          this.isError = true;
          this.isLoading = false;
        }
      });
    this.saveSettingsToLocalStorage();
  }

  getValue(entry: any, columnName: string): any {
    if (entry.hasOwnProperty(columnName)) {
      return entry[columnName];
    } else {
      return null;
    }
  }

  getId(entry: any): any {
    return this.getValue(entry, 'replay_id');
  }

  protected readonly countryCodeToFlag = countryCodeToFlag;
  protected readonly Math = Math;
  protected readonly formatDuration = formatDuration;

}
