import { HttpErrorResponse } from '@angular/common/http';
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, finalize, map, of, switchMap } from 'rxjs';

import { CreateTenantRequest, TenantDto, TenantModalMode, TenantStatus, TenantTableRow, UpdateTenantRequest } from '../../models/tenant.model';
import { CurrencyLookupDto, CurrencyService } from '../../../global-masters/currencies/public-api';
import { LanguageLookupDto, LanguageService } from '../../../global-masters/languages/public-api';
import { TimezoneLookupDto, TimezoneService } from '../../../global-masters/timezones/public-api';
import { TenantService } from '../../data-access/tenant.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 { Autocomplete } from '../../../../shared/components/form/autocomplete/autocomplete';
import { AutocompleteDisplayFn, AutocompleteResolveValueFn, AutocompleteSearchFn, AutocompleteValueFn } from '../../../../shared/components/form/autocomplete/autocomplete.types';
import { FormInput } from '../../../../shared/components/form/form-input/form-input';
import { FormSelect } from '../../../../shared/components/form/form-select/form-select';
import { FormSelectOption } from '../../../../shared/components/form/models/form-select.models';
import { Modal } from '../../../../shared/components/modal/modal';



@Component({
    selector: 'tenant-list',
    standalone: true,
    imports: [DataTable, Modal, ReactiveFormsModule, FormInput, FormSelect, Autocomplete],
    templateUrl: './tenant-list.html',
    styleUrl: './tenant-list.scss'
})
export class TenantList {
    private readonly destroyRef = inject(DestroyRef);
    private readonly tenantApi = inject(TenantService);
    private readonly languageApi = inject(LanguageService);
    private readonly currencyApi = inject(CurrencyService);
    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 tenants = signal<TenantTableRow[]>([]);
    readonly totalRecords = signal(0);
    readonly filteredRecords = signal(0);
    readonly saving = signal(false);
    readonly showTenantModal = signal(false);
    readonly tenantModalMode = signal<TenantModalMode>('create');
    readonly selectedTenantId = signal<string | null>(null);
    readonly selectedTenant = signal<TenantDto | null>(null);
    readonly tenantSubmitAttempted = signal(false);

    readonly tenantForm = this.formBuilder.group({
        code: this.formBuilder.nonNullable.control('', [Validators.required, Validators.maxLength(50)]),
        name: this.formBuilder.nonNullable.control('', [Validators.required, Validators.maxLength(150)]),
        status: this.formBuilder.control<TenantStatus | null>(TenantStatus.Trial, [Validators.required]),
        defaultLanguageId: this.formBuilder.control<string | null>(null, [Validators.required]),
        defaultCurrencyId: this.formBuilder.control<string | null>(null, [Validators.required]),
        defaultTimezoneId: this.formBuilder.control<string | null>(null, [Validators.required]),
        dataRegion: this.formBuilder.nonNullable.control('', [Validators.required, Validators.maxLength(100)]),
        isActive: this.formBuilder.control<number | null>(1, [Validators.required])
    });

    readonly tenantStatusOptions = signal<FormSelectOption<TenantStatus>[]>([
        { value: TenantStatus.Trial, label: 'Trial' },
        { value: TenantStatus.Active, label: 'Active' },
        { value: TenantStatus.Suspended, label: 'Suspended' },
        { value: TenantStatus.Cancelled, label: 'Cancelled' }
    ]);

    readonly activeStatusOptions = signal<FormSelectOption<number>[]>([
        { value: 1, label: 'Active' },
        { value: 0, label: 'Inactive' }
    ]);

    readonly searchLanguages: AutocompleteSearchFn<LanguageLookupDto> = (term, limit) =>
        this.languageApi.autocomplete(term, limit).pipe(
            catchError(() => {
                this.toastr.error('Unable to load languages.');
                return of<readonly LanguageLookupDto[]>([]);
            })
        );

    readonly searchCurrencies: AutocompleteSearchFn<CurrencyLookupDto> = (term, limit) =>
        this.currencyApi.autocomplete(term, limit).pipe(
            catchError(() => {
                this.toastr.error('Unable to load currencies.');
                return of<readonly CurrencyLookupDto[]>([]);
            })
        );

    readonly searchTimezones: AutocompleteSearchFn<TimezoneLookupDto> = (term, limit) =>
        this.timezoneApi.autocomplete(term, limit).pipe(
            catchError(() => {
                this.toastr.error('Unable to load timezones.');
                return of<readonly TimezoneLookupDto[]>([]);
            })
        );

    readonly displayLanguage: AutocompleteDisplayFn<LanguageLookupDto> = language => [language.code, language.name].filter(Boolean).join(' - ');

    readonly languageValue: AutocompleteValueFn<LanguageLookupDto, string> = language => language.id;

    readonly resolveLanguage: AutocompleteResolveValueFn<LanguageLookupDto, string> = value =>
        this.languageApi.getById(value).pipe(
            map(language => ({
                id: language.id,
                code: language.code,
                name: language.name,
                nativeName: language.nativeName,
                isRightToLeft: language.isRightToLeft
            }))
        );

    readonly displayCurrency: AutocompleteDisplayFn<CurrencyLookupDto> = currency => [currency.code, currency.name, currency.symbol ? `(${currency.symbol})` : '']
        .filter(Boolean)
        .join(' ');

    readonly currencyValue: AutocompleteValueFn<CurrencyLookupDto, string> = currency => currency.id;

    readonly resolveCurrency: AutocompleteResolveValueFn<CurrencyLookupDto, string> = value =>
        this.currencyApi.getCurrencyById(value).pipe(
            map(currency => ({
                id: currency.id,
                code: currency.code,
                name: currency.name,
                symbol: currency.symbol
            }))
        );

    readonly displayTimezone: AutocompleteDisplayFn<TimezoneLookupDto> = timezone => [timezone.ianaId, timezone.displayName].filter(Boolean).join(' — ');

    readonly timezoneValue: AutocompleteValueFn<TimezoneLookupDto, string> = timezone => timezone.id;

    readonly resolveTimezone: AutocompleteResolveValueFn<TimezoneLookupDto, string> = value =>
        this.timezoneApi.getById(value).pipe(
            map(timezone => ({
                id: timezone.id,
                ianaId: timezone.ianaId,
                displayName: timezone.displayName
            }))
        );

    readonly modalTitle = computed(() =>
        this.tenantModalMode() === 'create' ? 'Add Tenant' : 'Edit Tenant'
    );

    readonly submitLabel = computed(() =>
        this.tenantModalMode() === 'create' ? 'Save' : 'Update'
    );

    readonly loadingLabel = computed(() =>
        this.tenantModalMode() === 'create' ? 'Saving...' : 'Updating...'
    );

    readonly submitAction = computed<'save' | 'update'>(() =>
        this.tenantModalMode() === 'create' ? 'save' : 'update'
    );

    readonly columns = signal<DataTableColumn<TenantTableRow>[]>([
        { key: 'serialNumber', label: 'Sr. No.', header: 'Sr.No.', sortable: false, width: '100px' },
        { key: 'code', label: 'Code', header: 'Code', sortable: true, align: 'left' },
        { key: 'name', label: 'Tenant Name', header: 'Tenant Name', sortable: true, align: 'left' },
        {
            key: 'status',
            label: 'Status',
            header: 'Status',
            sortable: true,
            badge: true,
            badgeClass: value => this.getTenantStatusBadgeClass(value as TenantStatus),
            formatter: value => this.formatTenantStatus(value as TenantStatus)
        },
        { key: 'dataRegion', label: 'Data Region', header: 'Data Region', sortable: true, align: 'left' },
        {
            key: 'isActive',
            label: 'Active',
            header: 'Active',
            sortable: true,
            badge: true,
            badgeClass: value => value === true
                ? 'badge bg-success/10 text-success'
                : 'badge bg-danger/10 text-danger',
            formatter: value => value === true ? 'Active' : 'Inactive'
        },
        {
            key: 'createdOn',
            label: 'Created On',
            header: 'Created On',
            sortable: true,
            formatter: value => this.formatDateTime(value)
        }
    ]);

    readonly actions = signal<DataTableAction<TenantTableRow>[]>([
        {
            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
        },
        {
            type: 'activate',
            label: 'Activate',
            icon: 'ti ti-check',
            className: 'text-success',
            visible: row => !row.isActive
        }
    ]);

    constructor() {
        this.queryRequests$
            .pipe(
                switchMap(query =>
                    this.tenantApi.getTenantDataTable(this.buildTenantQuery(query)).pipe(
                        catchError(() => {
                            this.toastr.error('Unable to load tenants.');
                            this.clearTenantGrid();
                            return of(null);
                        })
                    )
                ),
                takeUntilDestroyed(this.destroyRef)
            )
            .subscribe(response => {
                if (!response) {
                    return;
                }

                const query = this.queryState.getQuery();

                if (response.draw !== query.draw) {
                    return;
                }

                const tenantsWithSerialNumbers: TenantTableRow[] = response.rows.map((tenant, index) => ({
                    ...tenant,
                    serialNumber: (query.page - 1) * query.pageSize + index + 1
                }));

                this.tenants.set(tenantsWithSerialNumbers);
                this.totalRecords.set(response.total);
                this.filteredRecords.set(response.filtered);
            });
    }

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

    loadTenants(query: DataTableQuery): void {
        this.queryRequests$.next(query);
    }

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

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

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

    onActionClick(event: DataTableActionEvent<TenantTableRow>): void {
        if (event.action.type === 'edit') {
            this.openEditTenant(event.row.id);
        }
    }

    onAddTenant(): void {
        this.tenantModalMode.set('create');
        this.selectedTenantId.set(null);
        this.selectedTenant.set(null);
        this.tenantSubmitAttempted.set(false);
        this.resetTenantForm({
            code: '',
            name: '',
            status: TenantStatus.Trial,
            defaultLanguageId: null,
            defaultCurrencyId: null,
            defaultTimezoneId: null,
            dataRegion: '',
            isActive: 1
        });
        this.showTenantModal.set(true);
    }

    closeTenantModal(): void {
        if (this.saving()) {
            return;
        }

        this.showTenantModal.set(false);
        this.selectedTenantId.set(null);
        this.selectedTenant.set(null);
        this.tenantSubmitAttempted.set(false);
        this.resetTenantForm({
            code: '',
            name: '',
            status: TenantStatus.Trial,
            defaultLanguageId: null,
            defaultCurrencyId: null,
            defaultTimezoneId: null,
            dataRegion: '',
            isActive: 1
        });
    }

    saveTenant(): void {
        if (this.tenantForm.invalid) {
            this.tenantSubmitAttempted.set(true);
            this.tenantForm.markAllAsTouched();
            this.focusFirstInvalidControl();
            return;
        }

        if (this.saving()) {
            return;
        }

        this.saving.set(true);

        if (this.tenantModalMode() === 'create') {
            this.tenantApi
                .createTenant(this.buildCreateTenantRequest())
                .pipe(
                    finalize(() => this.saving.set(false)),
                    takeUntilDestroyed(this.destroyRef)
                )
                .subscribe({
                    next: () => {
                        this.toastr.success('Tenant saved successfully.');
                        this.finishTenantSave();
                    },
                    error: (error: HttpErrorResponse) => this.handleSaveError(error)
                });

            return;
        }

        const tenantId = this.selectedTenantId();

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

        this.tenantApi
            .updateTenant(tenantId, this.buildUpdateTenantRequest())
            .pipe(
                finalize(() => this.saving.set(false)),
                takeUntilDestroyed(this.destroyRef)
            )
            .subscribe({
                next: () => {
                    this.toastr.success('Tenant updated successfully.');
                    this.finishTenantSave();
                },
                error: (error: HttpErrorResponse) => this.handleSaveError(error)
            });
    }

    openEditTenant(id: string): void {
        this.tenantModalMode.set('edit');
        this.selectedTenantId.set(id);
        this.selectedTenant.set(null);
        this.tenantSubmitAttempted.set(false);
        this.resetTenantForm({
            code: '',
            name: '',
            status: TenantStatus.Trial,
            defaultLanguageId: null,
            defaultCurrencyId: null,
            defaultTimezoneId: null,
            dataRegion: '',
            isActive: 1
        });

        this.tenantApi
            .getTenantById(id)
            .pipe(takeUntilDestroyed(this.destroyRef))
            .subscribe({
                next: tenant => {
                    if (this.selectedTenantId() !== tenant.id) {
                        return;
                    }

                    this.selectedTenant.set(tenant);
                    this.resetTenantForm({
                        code: tenant.code ?? '',
                        name: tenant.name ?? '',
                        status: tenant.status ?? TenantStatus.Trial,
                        defaultLanguageId: tenant.defaultLanguageId ?? null,
                        defaultCurrencyId: tenant.defaultCurrencyId ?? null,
                        defaultTimezoneId: tenant.defaultTimezoneId ?? null,
                        dataRegion: tenant.dataRegion ?? '',
                        isActive: tenant.isActive ? 1 : 0
                    });
                    this.showTenantModal.set(true);
                },
                error: (error: HttpErrorResponse) => {
                    if (error.status === 404) {
                        this.toastr.error('The tenant is no longer available.');
                    }

                    this.selectedTenantId.set(null);
                }
            });
    }

    private buildTenantQuery(query: DataTableQuery): DataTableQuery {
        return {
            ...query,
            sortBy: this.resolveSortField(query.sortBy ?? null)
        };
    }

    private resolveSortField(sortBy: string | null | undefined): string | null {
        if (!sortBy) {
            return null;
        }

        switch (sortBy) {
            case 'code':
            case 'name':
            case 'status':
            case 'dataRegion':
            case 'isActive':
            case 'createdOn':
                return sortBy;
            default:
                return null;
        }
    }

    private buildCreateTenantRequest(): CreateTenantRequest {
        const value = this.tenantForm.getRawValue();

        return {
            code: value.code.trim(),
            name: value.name.trim(),
            status: value.status ?? TenantStatus.Trial,
            defaultLanguageId: value.defaultLanguageId ?? '',
            defaultCurrencyId: value.defaultCurrencyId ?? '',
            defaultTimezoneId: value.defaultTimezoneId ?? '',
            dataRegion: value.dataRegion.trim()
        };
    }

    private buildUpdateTenantRequest(): UpdateTenantRequest {
        const value = this.tenantForm.getRawValue();

        return {
            code: value.code.trim(),
            name: value.name.trim(),
            status: value.status ?? TenantStatus.Trial,
            defaultLanguageId: value.defaultLanguageId ?? '',
            defaultCurrencyId: value.defaultCurrencyId ?? '',
            defaultTimezoneId: value.defaultTimezoneId ?? '',
            defaultDbConnectionId: this.selectedTenant()?.defaultDbConnectionId ?? null,
            dataRegion: value.dataRegion.trim(),
            isActive: value.isActive === 1
        };
    }

    private finishTenantSave(): void {
        this.showTenantModal.set(false);
        this.selectedTenantId.set(null);
        this.selectedTenant.set(null);
        this.tenantSubmitAttempted.set(false);
        this.resetTenantForm({
            code: '',
            name: '',
            status: TenantStatus.Trial,
            defaultLanguageId: null,
            defaultCurrencyId: null,
            defaultTimezoneId: null,
            dataRegion: '',
            isActive: 1
        });
        this.loadTenants(this.queryState.getQuery());
    }

    private resetTenantForm(value: {
        code: string;
        name: string;
        status: TenantStatus;
        defaultLanguageId: string | null;
        defaultCurrencyId: string | null;
        defaultTimezoneId: string | null;
        dataRegion: string;
        isActive: number | null;
    }): void {
        this.tenantForm.reset(value);
        this.tenantForm.markAsPristine();
        this.tenantForm.markAsUntouched();
        this.tenantForm.updateValueAndValidity();
    }

    private formatTenantStatus(status: TenantStatus): string {
        switch (status) {
            case TenantStatus.Trial:
                return 'Trial';
            case TenantStatus.Active:
                return 'Active';
            case TenantStatus.Suspended:
                return 'Suspended';
            case TenantStatus.Cancelled:
                return 'Cancelled';
            default:
                return 'Unknown';
        }
    }

    private getTenantStatusBadgeClass(status: TenantStatus): string {
        switch (status) {
            case TenantStatus.Trial:
                return 'badge bg-warning/10 text-warning';
            case TenantStatus.Active:
                return 'badge bg-success/10 text-success';
            case TenantStatus.Suspended:
                return 'badge bg-info/10 text-info';
            case TenantStatus.Cancelled:
                return 'badge bg-danger/10 text-danger';
            default:
                return 'badge bg-secondary/10 text-secondary';
        }
    }

    private formatDateTime(value: unknown): string {
        if (typeof value !== 'string' || value.trim().length === 0) {
            return '';
        }

        const date = new Date(value);

        if (Number.isNaN(date.getTime())) {
            return value;
        }

        return date.toLocaleString();
    }

    private handleSaveError(error: HttpErrorResponse): void {
        if (error.status === 409) {
            this.toastr.error('A tenant with this code already exists.', 'Duplicate tenant code');
        }
    }

    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 clearTenantGrid(): void {
        this.tenants.set([]);
        this.totalRecords.set(0);
        this.filteredRecords.set(0);
    }
}
