import { HttpErrorResponse } from '@angular/common/http';
import { Component, DestroyRef, ElementRef, OnInit, computed, inject, signal, viewChild } 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, finalize, of, switchMap } from 'rxjs';

import {
  CreateTimezoneRequest,
  TimezoneDto,
  TimezoneModalMode,
  UpdateTimezoneRequest
} from '../../models/timezone.model';
import { TimezoneService } from '../../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 { Modal } from '../../../../../shared/components/modal/modal';
import { DataTableCellDirective } from '../../../../../shared/directives/data-table-cell.directive';
import { ConfirmDialog } from '../../../../../shared/components/confirm-dialog/confirm-dialog';

interface TimezoneTableRow extends DataTableRecord {
  readonly id: string;
  readonly ianaId: string;
  readonly displayName: string;
  readonly utcOffsetMinutes: number;
  readonly isActive: boolean;
  readonly serialNumber: number;
  readonly createdOn: string;
  readonly modifiedOn: string | null;
}

@Component({
  selector: 'timezone-list',
  standalone: true,
  imports: [DataTable, DataTableCellDirective, Modal, ReactiveFormsModule, FormInput, ConfirmDialog],
  templateUrl: './timezone-list.html',
  styleUrl: './timezone-list.scss'
})
export class TimezoneList implements OnInit {
  private readonly destroyRef = inject(DestroyRef);
  private readonly timezoneApi = inject(TimezoneService);
  private readonly formBuilder = inject(FormBuilder);
  private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
  private readonly toastr = inject(ToastrService);
  private readonly queryRequests$ = new Subject<DataTableQuery>();

  readonly queryState = new DataTableQueryState();
  readonly timezones = signal<TimezoneTableRow[]>([]);
  readonly totalRecords = signal(0);
  readonly modalLoading = signal(false);
  readonly saving = signal(false);
  readonly statusChangingId = signal<string | null>(null);
  readonly showModal = signal(false);
  readonly modalMode = signal<TimezoneModalMode>('create');
  readonly selectedTimezoneId = signal<string | null>(null);
  readonly selectedTimezone = signal<TimezoneDto | null>(null);
  readonly submitAttempted = signal(false);
  readonly pendingDeleteTimezoneId = signal<string | null>(null);
  readonly deleteConfirmDialog = viewChild(ConfirmDialog);

  readonly timezoneForm = this.formBuilder.nonNullable.group({
    ianaId: ['', [
      Validators.required,
      Validators.maxLength(64),
      Validators.pattern(/^[A-Za-z]+(?:[._+-]?[A-Za-z0-9]+)*(?:\/[A-Za-z0-9._+-]+)+$/)
    ]],
    displayName: ['', [Validators.required, Validators.maxLength(128), Validators.pattern(/.*\S.*/)]],
    utcOffsetMinutes: [0, [Validators.required, Validators.min(-720), Validators.max(840)]]
  });

  readonly isViewMode = computed(() => this.modalMode() === 'view');
  readonly modalTitle = computed(() => {
    switch (this.modalMode()) {
      case 'create': return 'Add Timezone';
      case 'edit': return 'Edit Timezone';
      case 'view': return 'View Timezone';
    }
  });
  readonly submitLabel = computed(() => this.modalMode() === 'create' ? 'Save' : 'Update');
  readonly loadingLabel = computed(() => this.modalMode() === 'create' ? 'Saving...' : 'Updating...');
  readonly submitAction = computed<'save' | 'update'>(() => this.modalMode() === 'create' ? 'save' : 'update');

  readonly columns = signal<DataTableColumn<TimezoneTableRow>[]>([
    { key: 'serialNumber', label: 'Sr. No.', header: 'Sr.No.', sortable: false, width: '90px' },
    { key: 'ianaId', label: 'IANA ID', header: 'IANA ID', sortable: true, headerAlign: 'center', align: 'left' },
    { key: 'displayName', label: 'Display Name', header: 'Display Name', sortable: true, headerAlign: 'center', align: 'left' },
    {
      key: 'utcOffsetMinutes', label: 'UTC Offset', header: 'UTC Offset', sortable: true,
      formatter: value => this.formatUtcOffset(Number(value))
    },
    {
      key: 'isActive', label: 'Status', header: 'Status', sortable: true, badge: true,
      badgeClass: value => value === true
        ? 'badge bg-success/10 text-success'
        : 'badge bg-danger/10 text-danger',
      formatter: value => value ? 'Active' : 'Inactive'
    }
  ]);

  readonly actions = signal<DataTableAction<TimezoneTableRow>[]>([
    {
      type: 'edit',
      label: 'Edit',
      icon: 'ti ti-edit',
      className: 'text-primary'
    },
    {
      type: 'delete',
      label: 'Delete',
      icon: 'ti ti-trash',
      className: 'text-danger',
      visible: row => row.isActive,
      disabled: row => this.statusChangingId() === row.id
    },
    {
      type: 'activate',
      label: 'Activate',
      icon: 'ti ti-check',
      className: 'text-success',
      visible: row => !row.isActive,
      disabled: row => this.statusChangingId() === row.id
    }
  ]);

  constructor() {
    this.queryRequests$.pipe(
      switchMap(query => this.timezoneApi.getDataTable(query).pipe(
        catchError(() => {
          this.timezones.set([]);
          this.totalRecords.set(0);
          return of(null);
        })
      )),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe(response => {
      if (!response || response.draw !== this.queryState.getQuery().draw) return;
      const query = this.queryState.getQuery();
      this.timezones.set(response.rows.map((timezone, index) => ({
        ...timezone,
        serialNumber: (query.page - 1) * query.pageSize + index + 1
      })));
      this.totalRecords.set(response.total);
    });
  }

  ngOnInit(): void { this.loadTimezones(this.queryState.getQuery()); }
  loadTimezones(query: DataTableQuery): void { this.queryRequests$.next(query); }
  onSearch(value: string): void { this.loadTimezones(this.queryState.setSearch(value.trim())); }
  onPageChange(event: DataTablePageEvent): void { this.loadTimezones(this.queryState.setPage(event)); }
  onSortChange(event: DataTableSortEvent): void { this.loadTimezones(this.queryState.setSort(event)); }

  onDeleteConfirmed(): void {
    const timezoneId = this.pendingDeleteTimezoneId();

    if (!timezoneId) {
      return;
    }

    this.pendingDeleteTimezoneId.set(null);
    this.changeTimezoneStatus(timezoneId, false);
  }

  onDeleteCancelled(): void {
    this.pendingDeleteTimezoneId.set(null);
  }

  onActionClick(event: DataTableActionEvent<TimezoneTableRow>): void {
    if (event.action.type === 'view') this.openTimezone(event.row.id, 'view');
    if (event.action.type === 'edit') this.openTimezone(event.row.id, 'edit');
    if (event.action.type === 'delete') this.requestDeleteTimezone(event.row.id);
    if (event.action.type === 'activate') this.changeTimezoneStatus(event.row.id, true);
  }

  private requestDeleteTimezone(id: string): void {
    this.pendingDeleteTimezoneId.set(id);
    this.deleteConfirmDialog()?.open();
  }

  onAddTimezone(): void {
    this.modalMode.set('create');
    this.prepareModal(null);
    this.showModal.set(true);
  }

  openTimezone(id: string, mode: 'edit' | 'view'): void {
    this.modalMode.set(mode);
    this.prepareModal(id);
    this.modalLoading.set(true);
    this.showModal.set(true);
    this.timezoneApi.getById(id).pipe(
      finalize(() => this.modalLoading.set(false)),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe({
      next: timezone => {
        if (this.selectedTimezoneId() !== timezone.id || !this.showModal()) return;
        this.selectedTimezone.set(timezone);
        this.timezoneForm.reset({
          ianaId: timezone.ianaId,
          displayName: timezone.displayName,
          utcOffsetMinutes: timezone.utcOffsetMinutes
        });
        if (mode === 'view') this.timezoneForm.disable();
        this.resetFormState();
      },
      error: (error: HttpErrorResponse) => {
        this.showModal.set(false);
        if (error.status === 404) this.toastr.error('The timezone is no longer available.');
      }
    });
  }

  closeModal(): void {
    if (this.saving()) return;
    this.showModal.set(false);
    this.selectedTimezoneId.set(null);
    this.selectedTimezone.set(null);
    this.submitAttempted.set(false);
    this.timezoneForm.enable();
  }

  saveTimezone(): void {
    if (this.isViewMode() || this.saving() || this.modalLoading()) return;
    if (this.timezoneForm.invalid) {
      this.submitAttempted.set(true);
      this.timezoneForm.markAllAsTouched();
      this.focusFirstInvalidControl();
      return;
    }
    const selected = this.selectedTimezone();
    const id = this.selectedTimezoneId();
    if (this.modalMode() === 'edit' && (!selected || !id)) return;

    this.saving.set(true);
    const createRequest = this.buildCreateRequest();
    const operation = this.modalMode() === 'create'
      ? this.timezoneApi.create(createRequest)
      : this.timezoneApi.update(id!, { ...createRequest, isActive: selected!.isActive });
    operation.pipe(
      finalize(() => this.saving.set(false)),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe({
      next: () => {
        this.toastr.success(this.modalMode() === 'create'
          ? 'Timezone saved successfully.'
          : 'Timezone updated successfully.');
        this.finishSave();
      },
      error: (error: HttpErrorResponse) => this.handleSaveError(error)
    });
  }

  formatUtcOffset(minutes: number): string {
    const sign = minutes >= 0 ? '+' : '-';
    const absolute = Math.abs(minutes);
    return `UTC${sign}${String(Math.floor(absolute / 60)).padStart(2, '0')}:${String(absolute % 60).padStart(2, '0')}`;
  }

  private prepareModal(id: string | null): void {
    this.selectedTimezoneId.set(id);
    this.selectedTimezone.set(null);
    this.submitAttempted.set(false);
    this.timezoneForm.enable();
    this.timezoneForm.reset({ ianaId: '', displayName: '', utcOffsetMinutes: 0 });
    this.resetFormState();
  }

  private buildCreateRequest(): CreateTimezoneRequest {
    const value = this.timezoneForm.getRawValue();
    return {
      ianaId: value.ianaId.trim(),
      displayName: value.displayName.trim(),
      utcOffsetMinutes: value.utcOffsetMinutes
    };
  }

  private changeTimezoneStatus(id: string, isActive: boolean): void {
    if (this.statusChangingId()) return;
    this.statusChangingId.set(id);
    this.timezoneApi.getById(id).pipe(
      switchMap(timezone => this.timezoneApi.update(id, {
        ianaId: timezone.ianaId,
        displayName: timezone.displayName,
        utcOffsetMinutes: timezone.utcOffsetMinutes,
        isActive
      })),
      finalize(() => this.statusChangingId.set(null)),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe({
      next: () => {
        this.toastr.success(isActive
          ? 'Timezone activated successfully.'
          : 'Timezone deleted successfully.');
        this.loadTimezones(this.queryState.getQuery());
      },
      error: (error: HttpErrorResponse) => {
        if (error.status === 404) this.toastr.error('The timezone is no longer available.');
      }
    });
  }

  private handleSaveError(error: HttpErrorResponse): void {
    if (error.status === 409) {
      const message = this.apiErrorMessage(error) ?? 'A timezone with this IANA ID already exists.';
      this.toastr.error(message, 'Duplicate IANA timezone ID');
    } else if (error.status === 404) {
      this.toastr.error('The timezone is no longer available.');
      this.closeModal();
    }
  }

  private apiErrorMessage(error: HttpErrorResponse): string | null {
    const body: unknown = error.error;
    if (!body || typeof body !== 'object') return null;
    if ('detail' in body && typeof body.detail === 'string') return body.detail;
    if ('message' in body && typeof body.message === 'string') return body.message;
    return null;
  }

  private finishSave(): void {
    this.showModal.set(false);
    this.selectedTimezoneId.set(null);
    this.selectedTimezone.set(null);
    this.submitAttempted.set(false);
    this.loadTimezones(this.queryState.getQuery());
  }

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

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