import { Component, DestroyRef, ElementRef, computed, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import {
  Subject,
  catchError,
  debounceTime,
  distinctUntilChanged,
  finalize,
  map,
  of,
  switchMap,
  take
} from 'rxjs';

import {
  CityDto,
  CityModalMode,
  CreateCityRequest,
  UpdateCityRequest
} from '../../models/city.model';
import { CountryLookupDto } from '../../../countries/models/country.model';
import { StateLookupDto } from '../../../states/models/state.model';
import { TimezoneDto, TimezoneLookupDto } from '../../../timezones/models/timezone.model';
import { CityService } from '../../../cities/data-access/city.service';
import { CountryService } from '../../../countries/data-access/country.service';
import { StateService } from '../../../states/data-access/state.service';
import { TimezoneService } from '../../../timezones/data-access/timezone.service';
import { DataTable } from '../../../../../shared/components/data-table/data-table';
import { DataTableQueryState } from '../../../../../shared/components/data-table/data-table-query.state';
import {
  DataTableAction,
  DataTableActionEvent,
  DataTableColumn,
  DataTablePageEvent,
  DataTableQuery,
  DataTableRecord,
  DataTableSortEvent
} from '../../../../../shared/components/data-table/data-table.types';
import { FormInput } from '../../../../../shared/components/form/form-input/form-input';
import { Autocomplete } from '../../../../../shared/components/form/autocomplete/autocomplete';
import {
  AutocompleteDisplayFn,
  AutocompleteResolveValueFn,
  AutocompleteSearchFn,
  AutocompleteValueFn
} from '../../../../../shared/components/form/autocomplete/autocomplete.types';
import { Modal } from '../../../../../shared/components/modal/modal';
import { FilterCard } from '../../../../../shared/components/filter-card/filter-card';

interface CityTableRow extends DataTableRecord {
  id: string;
  stateId: string;
  name: string;
  code: string | null;
  timezoneId: string | null;
  isActive: boolean;
  serialNumber: number;
  state: string;
  country: string;
  createdOn?: string;
  modifiedOn?: string | null;
}

@Component({
  selector: 'city-list',
  standalone: true,
  imports: [DataTable, Modal, ReactiveFormsModule, FormInput, Autocomplete, FilterCard],
  templateUrl: './city-list.html',
  styleUrl: './city-list.scss'
})
export class CityList {
  private readonly destroyRef = inject(DestroyRef);
  private readonly cityApi = inject(CityService);
  private readonly countryApi = inject(CountryService);
  private readonly stateApi = inject(StateService);
  private readonly timezoneApi = inject(TimezoneService);
  private readonly formBuilder = inject(FormBuilder);
  private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
  private readonly toastr = inject(ToastrService);
  private readonly cityQueryRequests$ = new Subject<DataTableQuery>();

  readonly queryState = new DataTableQueryState();
  readonly cities = signal<CityTableRow[]>([]);
  readonly selectedCountryId = signal<string | null>(null);
  readonly selectedStateId = signal<string | null>(null);
  readonly appliedCountryId = signal<string | null>(null);
  readonly appliedStateId = signal<string | null>(null);
  readonly selectedCountry = signal<CountryLookupDto | null>(null);
  readonly selectedFilterState = signal<StateLookupDto | null>(null);
  readonly selectedFormCountry = signal<CountryLookupDto | null>(null);
  readonly selectedFormState = signal<StateLookupDto | null>(null);
  readonly totalRecords = signal(0);
  readonly saving = signal(false);
  readonly showCityModal = signal(false);
  readonly modalMode = signal<CityModalMode>('create');
  readonly selectedCity = signal<CityDto | null>(null);
  readonly submitAttempted = signal(false);

  readonly filterForm = this.formBuilder.nonNullable.group({
    countryId: [''],
    stateId: [{ value: '', disabled: true }]
  });

  readonly cityForm = this.formBuilder.nonNullable.group({
    countryId: ['', Validators.required],
    stateId: ['', Validators.required],
    name: ['', [Validators.required, Validators.maxLength(150)]],
    code: [
      '',
      [
        Validators.required,
        Validators.maxLength(16),
        Validators.pattern(/^[A-Za-z0-9_-]+$/)
      ]
    ],
    timezoneId: this.formBuilder.control<string | null>(null)
  });

  readonly searchTimezones: AutocompleteSearchFn<TimezoneLookupDto> =
    (term, limit) => this.timezoneApi.autocomplete(term, limit);
  readonly searchCountries: AutocompleteSearchFn<CountryLookupDto> =
    (term, limit) => this.countryApi.autocomplete(term, limit).pipe(
      catchError(() => {
        this.toastr.error('Unable to load countries.');
        return of<CountryLookupDto[]>([]);
      })
    );
  readonly searchFilterStates: AutocompleteSearchFn<StateLookupDto> =
    (term, limit) => {
      const countryId = this.selectedCountryId();
      if (!countryId) return of<StateLookupDto[]>([]);
      return this.stateApi.autocomplete(countryId, term, limit).pipe(
        catchError(() => {
          this.toastr.error('Unable to load states.');
          return of<StateLookupDto[]>([]);
        })
      );
    };
  readonly searchFormStates: AutocompleteSearchFn<StateLookupDto> =
    (term, limit) => {
      const countryId = this.cityForm.controls.countryId.value;
      if (!countryId) return of<StateLookupDto[]>([]);
      return this.stateApi.autocomplete(countryId, term, limit).pipe(
        catchError(() => {
          this.toastr.error('Unable to load states.');
          return of<StateLookupDto[]>([]);
        })
      );
    };
  readonly displayCountry: AutocompleteDisplayFn<CountryLookupDto> = country => country.name;
  readonly countryValue: AutocompleteValueFn<CountryLookupDto, string> = country => country.id;
  readonly resolveCountry: AutocompleteResolveValueFn<CountryLookupDto, string> =
    value => this.countryApi.getCountryById(value).pipe(
      map(country => ({ id: country.id, iso2: country.iso2, name: country.name }))
    );
  readonly displayState: AutocompleteDisplayFn<StateLookupDto> = state => state.name;
  readonly stateValue: AutocompleteValueFn<StateLookupDto, string> = state => state.id;
  readonly resolveState: AutocompleteResolveValueFn<StateLookupDto, string> =
    value => this.stateApi.getStateById(value).pipe(
      map(state => ({ id: state.id, name: state.name, code: state.code ?? '' }))
    );
  readonly displayTimezone: AutocompleteDisplayFn<TimezoneLookupDto> =
    timezone => `${timezone.ianaId} — ${timezone.displayName}`;
  readonly timezoneValue: AutocompleteValueFn<TimezoneLookupDto, string> =
    timezone => timezone.id;
  readonly resolveTimezone: AutocompleteResolveValueFn<TimezoneLookupDto, string> =
    value => this.timezoneApi.getById(value).pipe(map(timezone => this.toTimezoneLookup(timezone)));
  readonly filterStatePlaceholder = computed(() =>
    this.selectedCountryId() ? 'Search' : 'Select a country first'
  );
  readonly formStatePlaceholder = computed(() =>
    this.cityForm.controls.countryId.value ? 'Search' : 'Search'
  );
  readonly emptyMessage = computed(() =>
    this.appliedCountryId() && this.appliedStateId()
      ? 'No cities found'
      : 'Select a country and state'
  );

  readonly emptyDescription = computed(() =>
    this.appliedCountryId() && this.appliedStateId()
      ? 'There are no cities available for the selected state.'
      : 'Choose a country and state to view available cities.'
  );
  readonly modalTitle = computed(() => {
    const mode = this.modalMode();
    return mode === 'create' ? 'Add City' : mode === 'edit' ? 'Edit City' : 'View City';
  });
  readonly submitLabel = computed(() =>
    this.modalMode() === 'create' ? 'Save' : 'Update'
  );
  readonly loadingLabel = computed(() =>
    this.modalMode() === 'create' ? 'Saving...' : 'Updating...'
  );

  readonly columns = signal<DataTableColumn<CityTableRow>[]>([
    { key: 'serialNumber', label: 'Sr. No.', header: 'Sr. No.', sortable: false, width: '70px' },
    { key: 'name', label: 'City Name', header: 'City Name', sortable: true, align: 'left' },
    { key: 'code', label: 'Code', header: 'Code', sortable: true },
    { key: 'stateName', label: 'State', header: 'State', sortable: false },
    { key: 'countryName', label: 'Country', header: 'Country', sortable: false },
    {
      key: 'isActive',
      label: 'Status',
      header: 'Status',
      sortable: true,
      badge: true,
      width: '100px',
      formatter: value => value ? 'Active' : 'Inactive',
      badgeClass: value => value === true
        ? 'badge bg-success/10 text-success'
        : 'badge bg-danger/10 text-danger'
    }
  ]);

  readonly actions = signal<DataTableAction<CityTableRow>[]>([
    // { type: 'view', label: 'View', icon: 'ti ti-eye', className: 'text-info' },
    { type: 'edit', label: 'Edit', icon: 'ti ti-edit', className: 'text-primary' },
    {
      type: 'deactivate',
      label: 'Deactivate',
      icon: 'ti ti-ban',
      className: 'text-danger',
      visible: row => row.isActive
    },
    {
      type: 'activate',
      label: 'Activate',
      icon: 'ti ti-check',
      className: 'text-success',
      visible: row => !row.isActive
    }
  ]);

  constructor() {
    this.configureCityQueries();
    this.configureFilterChanges();
    this.configureFormChanges();
  }

  ngOnInit(): void {
    this.loadCities(this.queryState.getQuery());
  }

  onFilterCountrySelected(country: CountryLookupDto): void {
    this.selectedCountry.set(country);
  }

  onFilterCountryCleared(): void {
    this.selectedCountry.set(null);
  }

  onFilterStateSelected(state: StateLookupDto): void {
    this.selectedFilterState.set(state);
  }

  onFilterStateCleared(): void {
    this.selectedFilterState.set(null);
  }

  applyCityFilters(): void {
    const countryId = this.filterForm.controls.countryId.value || null;
    const stateId = this.filterForm.controls.stateId.value || null;

    this.appliedCountryId.set(countryId);
    this.appliedStateId.set(stateId);

    this.loadCities(this.queryState.setPage({
      pageIndex: 1,
      pageSize: this.queryState.pageSize()
    }));
  }

  onFormCountrySelected(country: CountryLookupDto): void {
    this.selectedFormCountry.set(country);
  }

  onFormCountryCleared(): void {
    this.selectedFormCountry.set(null);
  }

  onFormStateSelected(state: StateLookupDto): void {
    this.selectedFormState.set(state);
  }

  onFormStateCleared(): void {
    this.selectedFormState.set(null);
  }

  loadCities(query: DataTableQuery): void {
    this.cityQueryRequests$.next(query);
  }

  onSearch(value: string): void {
    this.loadCities(this.queryState.setSearch(value.trim()));
  }

  onPageChange(event: DataTablePageEvent): void {
    this.loadCities(this.queryState.setPage(event));
  }

  onSortChange(event: DataTableSortEvent): void {
    this.loadCities(this.queryState.setSort(event));
  }

  onActionClick(event: DataTableActionEvent<CityTableRow>): void {
    const city = this.toCityDto(event.row);
    switch (event.action.type) {
      case 'edit':
        this.openExistingCity(city, 'edit');
        break;
      case 'activate':
        this.updateCityStatus(city, true);
        break;
      case 'deactivate':
        this.updateCityStatus(city, false);
        break;
    }
  }

  onAddCity(): void {
    this.modalMode.set('create');
    this.selectedCity.set(null);
    this.submitAttempted.set(false);
    this.selectedFormCountry.set(null);
    this.selectedFormState.set(null);
    this.cityForm.enable({ emitEvent: false });
    this.cityForm.reset({ countryId: '', stateId: '', name: '', code: '', timezoneId: null }, { emitEvent: false });
    this.resetFormState();
    this.showCityModal.set(true);
  }

  closeCityModal(): void {
    if (this.saving()) return;
    this.showCityModal.set(false);
    this.selectedCity.set(null);
    this.selectedFormCountry.set(null);
    this.selectedFormState.set(null);
    this.submitAttempted.set(false);
  }

  saveCity(): void {
    if (this.saving()) return;
    if (this.cityForm.invalid) {
      this.submitAttempted.set(true);
      this.cityForm.markAllAsTouched();
      this.focusFirstInvalidControl();
      return;
    }

    this.saving.set(true);
    const city = this.selectedCity();
    const request$ = this.modalMode() === 'create'
      ? this.cityApi.createCity(this.buildCreateRequest())
      : city
        ? this.cityApi.updateCity(city.id, this.buildUpdateRequest(city.isActive))
        : null;

    if (!request$) {
      this.saving.set(false);
      return;
    }

    request$.pipe(finalize(() => this.saving.set(false))).subscribe({
      next: () => {
        this.toastr.success(
          this.modalMode() === 'create'
            ? 'City saved successfully.'
            : 'City updated successfully.'
        );
        this.showCityModal.set(false);
        this.selectedCity.set(null);
        this.loadCities(this.queryState.getQuery());
      }
    });
  }

  private configureCityQueries(): void {
    this.cityQueryRequests$.pipe(
      switchMap(query => {
        const stateId = this.appliedStateId();
        const countryId = this.appliedCountryId();
        return this.cityApi.getCityDataTable(query, countryId, stateId).pipe(
              catchError(() => {
                this.toastr.error('Unable to load cities.');
                this.clearGrid();
                return of(null);
              })
            );
      }),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe(response => {
      if (!response) return;
      const query = this.queryState.getQuery();
      if (response.draw !== query.draw) return;

      this.cities.set(response.rows.map((city, index) => ({
        ...city,
        serialNumber: (query.page - 1) * query.pageSize + index + 1
      })));
      this.totalRecords.set(response.filtered);
    });
  }

  private configureFilterChanges(): void {
    this.filterForm.controls.countryId.valueChanges.pipe(
      distinctUntilChanged(),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe(countryId => {
      if (!countryId || this.selectedCountry()?.id !== countryId) {
        this.selectedCountry.set(null);
      }

      this.selectedCountryId.set(countryId || null);
      this.selectedStateId.set(null);
      this.selectedFilterState.set(null);

      this.filterForm.controls.stateId.reset('', { emitEvent: false });
      countryId
        ? this.filterForm.controls.stateId.enable({ emitEvent: false })
        : this.filterForm.controls.stateId.disable({ emitEvent: false });

    });

    this.filterForm.controls.stateId.valueChanges.pipe(
      distinctUntilChanged(),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe(stateId => {
      if (!stateId || this.selectedFilterState()?.id !== stateId) {
        this.selectedFilterState.set(null);
      }

      this.selectedStateId.set(stateId || null);
    });
  }

  private configureFormChanges(): void {
    this.cityForm.controls.countryId.valueChanges.pipe(
      distinctUntilChanged(),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe(countryId => {
      if (!this.showCityModal() || this.modalMode() !== 'create') return;

      if (!countryId || this.selectedFormCountry()?.id !== countryId) {
        this.selectedFormCountry.set(null);
      }

      this.selectedFormState.set(null);
      this.cityForm.controls.stateId.reset('', { emitEvent: false });
      this.cityForm.controls.stateId.enable({ emitEvent: false });
    });
  }

  private openExistingCity(city: CityDto, mode: 'edit'): void {
    this.cityApi.getCityById(city.id).pipe(
      switchMap(details => this.stateApi.getStateById(details.stateId).pipe(
        map(stateDetails => ({ details, countryId: stateDetails.countryId }))
      )),
      take(1)
    ).subscribe(({ details, countryId }) => {
      this.selectedCity.set(details);
      this.modalMode.set(mode);
      this.submitAttempted.set(false);
      this.selectedFormCountry.set(null);
      this.selectedFormState.set(null);
      this.cityForm.enable({ emitEvent: false });
      this.cityForm.reset({
        countryId,
        stateId: details.stateId,
        name: details.name ?? '',
        code: details.code ?? '',
        timezoneId: details.timezoneId
      }, { emitEvent: false });
      // this.cityForm.controls.countryId.disable({ emitEvent: false });
      // this.cityForm.controls.stateId.disable({ emitEvent: false });
      this.resetFormState();
      this.showCityModal.set(true);
    });
  }

  private updateCityStatus(city: CityDto, isActive: boolean): void {
    this.cityApi.updateCity(city.id, {
      name: city.name.trim(),
      code: city.code?.trim().toUpperCase() ?? '',
      timezoneId: city.timezoneId,
      isActive
    }).subscribe(() => {
      this.toastr.success(
        isActive ? 'City activated successfully.' : 'City deactivated successfully.'
      );
      this.loadCities(this.queryState.getQuery());
    });
  }

  private buildCreateRequest(): CreateCityRequest {
    const value = this.cityForm.getRawValue();
    return {
      stateId: value.stateId,
      name: value.name.trim(),
      code: value.code.trim().toUpperCase(),
      timezoneId: value.timezoneId
    };
  }

  private buildUpdateRequest(isActive: boolean): UpdateCityRequest {
    const value = this.cityForm.getRawValue();
    return {
      name: value.name.trim(),
      code: value.code.trim().toUpperCase(),
      timezoneId: value.timezoneId,
      isActive
    };
  }

  private clearGrid(): void {
    this.cities.set([]);
    this.totalRecords.set(0);
  }

  private resetFormState(): void {
    this.cityForm.markAsPristine();
    this.cityForm.markAsUntouched();
    this.cityForm.updateValueAndValidity();
  }

  private focusFirstInvalidControl(): void {
    queueMicrotask(() => {
      const control = this.elementRef.nativeElement.querySelector<HTMLElement>(
        'modal [data-form-control][aria-invalid="true"]'
      );
      control?.focus();
      control?.scrollIntoView({ behavior: 'smooth', block: 'center' });
    });
  }

  private toCityDto(row: CityTableRow): CityDto {
    return {
      id: row.id,
      stateId: row.stateId,
      name: row.name,
      state: row.state,
      country: row.country,
      code: row.code,
      timezoneId: row.timezoneId,
      isActive: row.isActive,
      createdOn: row.createdOn,
      modifiedOn: row.modifiedOn
    };
  }

  private toTimezoneLookup(timezone: TimezoneDto): TimezoneLookupDto {
    return {
      id: timezone.id,
      ianaId: timezone.ianaId,
      displayName: timezone.displayName
    };
  }
}
