import { CurrencyPipe, registerLocaleData } from '@angular/common';
import localeDe from '@angular/common/locales/de';
import { Component, LOCALE_ID, computed, effect, inject, signal } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialog } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { AgGridAngular } from 'ag-grid-angular';
import type {
  ColDef,
  GetRowIdParams,
  GridApi,
  GridReadyEvent,
  IDatasource,
  IGetRowsParams,
} from 'ag-grid-community';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { TeamPermissionsService } from '../../../core/team/team-permissions';
import { PenaltyApi } from '../../../core/team/penalty-api';
import { TeamStore } from '../../../core/team/team-store';
import { TransactionsApi } from '../../../core/team/transactions-api';
import { Penalty } from '../../../models/penalty.model';
import {
  CreatePlayerTransaction,
  CreateTeamWalletTransaction,
  TeamActivity,
  TransactionsJournalQuery,
  TransactionsSortField,
} from '../../../models/transaction.model';
import { ConfirmDialog, ConfirmDialogData } from '../../../shared/confirm-dialog/confirm-dialog';
import { ContextHelp } from '../../../shared/context-help/context-help';
import '../../../shared/ag-grid/ag-grid-modules';
import { teamwalletGridTheme } from '../../../shared/ag-grid/ag-grid-theme';
import { AmountCellRenderer } from '../../../shared/ag-grid/amount-cell-renderer';
import {
  ReverseActionCellRenderer,
  ReverseActionCellRendererParams,
} from '../../../shared/ag-grid/reverse-action-cell-renderer';
import { splitAmounts } from './transaction-calculation';

registerLocaleData(localeDe);

const HIGH_AMOUNT_CONFIRM_THRESHOLD = 300;

const JOURNAL_TYPE_OPTIONS: { value: string; label: string }[] = [
  { value: '', label: 'Alle Typen' },
  { value: 'payment', label: 'Zahlung' },
  { value: 'credit', label: 'Guthaben' },
  { value: 'fine', label: 'Strafe' },
  { value: 'levy', label: 'Umlage' },
  { value: 'fee', label: 'Gebühr' },
  { value: 'expense', label: 'Ausgabe' },
];

@Component({
  selector: 'app-cashbox',
  imports: [
    CurrencyPipe,
    ReactiveFormsModule,
    MatButtonModule,
    MatCardModule,
    MatCheckboxModule,
    MatFormFieldModule,
    MatIconModule,
    MatInputModule,
    MatSelectModule,
    MatSnackBarModule,
    ContextHelp,
    AgGridAngular,
  ],
  providers: [{ provide: LOCALE_ID, useValue: 'de-DE' }],
  templateUrl: './cashbox.html',
  styleUrl: './cashbox.scss',
})
export class Cashbox {
  private readonly dialog = inject(MatDialog);
  private readonly formBuilder = inject(FormBuilder);
  private readonly penaltyApi = inject(PenaltyApi);
  private readonly permissions = inject(TeamPermissionsService);
  private readonly route = inject(ActivatedRoute);
  private readonly router = inject(Router);
  private readonly snackBar = inject(MatSnackBar);
  private readonly teamStore = inject(TeamStore);
  private readonly transactionsApi = inject(TransactionsApi);
  private loadedTeamId: number | null = null;
  private pendingPenaltyId: number | null =
    Number(this.route.snapshot.queryParamMap.get('penaltyId')) || null;

  protected readonly team = this.teamStore.team;
  protected readonly penalties = signal<Penalty[]>([]);
  protected readonly saving = signal(false);
  protected readonly playerTransactionTypes = [
    { id: 0, label: 'Zahlung' },
    { id: 1, label: 'Guthaben' },
    { id: 11, label: 'Strafe' },
    { id: 12, label: 'Umlage' },
    { id: 13, label: 'Gebühr' },
  ];
  protected readonly teamTransactionTypes = [
    { id: 1, label: 'Guthaben' },
    { id: 14, label: 'Ausgabe' },
  ];

  // --- Kassenjournal (AG Grid) ---
  protected readonly gridTheme = teamwalletGridTheme;
  protected readonly journalTypeOptions = JOURNAL_TYPE_OPTIONS;
  protected readonly journalTypeFilter = signal('');
  protected readonly journalSearch = signal('');
  protected readonly journalTotal = signal<number | null>(null);
  private readonly journalSearchInput$ = new Subject<string>();
  private gridApi?: GridApi<TeamActivity>;
  private journalTeamId: number | null = null;

  protected readonly journalColumnDefs: ColDef<TeamActivity>[] = [
    {
      headerName: '',
      colId: 'icon',
      sortable: false,
      resizable: false,
      width: 56,
      cellRenderer: (params: { data?: TeamActivity }) => {
        const isTeam = !!params.data?.isTeamWalletTransaction;
        const span = document.createElement('span');
        span.className = 'material-icons cashbox-grid-icon' + (isTeam ? ' team' : '');
        span.textContent = isTeam ? 'account_balance' : 'person';
        return span;
      },
    },
    {
      headerName: 'Name',
      field: 'playerName',
      minWidth: 140,
      flex: 1,
      valueGetter: (params) => params.data?.playerName || 'Teamkasse',
    },
    {
      headerName: 'Typ',
      field: 'type',
      width: 130,
      valueFormatter: (params) => this.typeLabel(params.value),
    },
    {
      headerName: 'Datum',
      field: 'date',
      width: 120,
      valueFormatter: (params) =>
        params.value ? new Intl.DateTimeFormat('de-DE').format(new Date(params.value)) : '',
    },
    {
      headerName: 'Notiz',
      field: 'note',
      colId: 'note',
      minWidth: 160,
      flex: 2,
    },
    {
      headerName: 'Betrag',
      field: 'amount',
      width: 150,
      cellRenderer: AmountCellRenderer,
    },
    {
      headerName: '',
      colId: 'actions',
      width: 60,
      sortable: false,
      resizable: false,
      cellRenderer: ReverseActionCellRenderer,
      cellRendererParams: {
        canReverse: (activity: TeamActivity) => this.canReverse(activity),
        onReverse: (activity: TeamActivity) => this.reverseBooking(activity),
      } satisfies Partial<ReverseActionCellRendererParams>,
    },
  ];

  protected readonly journalGetRowId = (params: GetRowIdParams<TeamActivity>) =>
    `${params.data.isTeamWalletTransaction ? 'w' : 'p'}-${params.data.id}`;

  protected readonly canBook = computed(() =>
    this.permissions.canDo(this.team(), 'transactionCreate'),
  );

  protected readonly activePlayers = computed(() =>
    (this.team()?.players ?? []).filter((player) => player.active),
  );

  protected readonly playerForm = this.formBuilder.nonNullable.group({
    playerIds: this.formBuilder.nonNullable.control<number[]>([], Validators.required),
    amount: [0, [Validators.required, Validators.min(0.01), Validators.max(10000)]],
    type: [11, Validators.required],
    note: [''],
    date: [this.today(), Validators.required],
    total: [false],
  });

  protected readonly teamForm = this.formBuilder.nonNullable.group({
    amount: [0, [Validators.required, Validators.min(0.01), Validators.max(10000)]],
    type: [14, Validators.required],
    note: [''],
    date: [this.today(), Validators.required],
  });

  constructor() {
    effect(() => {
      const teamId = this.team()?.id;
      if (teamId && teamId !== this.loadedTeamId) {
        this.loadedTeamId = teamId;
        this.loadPenalties(teamId);
      }
      if (teamId && teamId !== this.journalTeamId) {
        this.journalTeamId = teamId;
        this.refreshJournal();
      }
    });
    effect(() => {
      if (this.pendingPenaltyId === null) return;
      const penalty = this.penalties().find((p) => p.id === this.pendingPenaltyId);
      if (!penalty) return;
      this.pendingPenaltyId = null;
      this.applyPenaltyPreset(penalty);
      void this.router.navigate([], { queryParams: {}, replaceUrl: true });
    });

    this.journalSearchInput$
      .pipe(debounceTime(300), takeUntilDestroyed())
      .subscribe((value) => {
        this.journalSearch.set(value);
        this.refreshJournal();
      });
  }

  private readonly narrowLayout =
    typeof window !== 'undefined' && typeof window.matchMedia === 'function'
      ? window.matchMedia('(max-width: 650px)')
      : null;

  protected onJournalGridReady(event: GridReadyEvent<TeamActivity>): void {
    this.gridApi = event.api;
    this.updateResponsiveColumns();
    this.narrowLayout?.addEventListener('change', () => this.updateResponsiveColumns());
    if (this.journalTeamId) this.setJournalDatasource(this.journalTeamId);
  }

  private updateResponsiveColumns(): void {
    this.gridApi?.setColumnsVisible(['note'], !(this.narrowLayout?.matches ?? false));
  }

  protected onJournalTypeChange(value: string): void {
    this.journalTypeFilter.set(value);
    this.refreshJournal();
  }

  protected onJournalSearchInput(value: string): void {
    this.journalSearchInput$.next(value);
  }

  private refreshJournal(): void {
    if (!this.journalTeamId) return;
    this.setJournalDatasource(this.journalTeamId);
  }

  private setJournalDatasource(teamId: number): void {
    if (!this.gridApi) return;
    this.gridApi.setGridOption('datasource', this.buildJournalDatasource(teamId));
  }

  private buildJournalDatasource(teamId: number): IDatasource {
    return {
      getRows: (params: IGetRowsParams) => {
        const limit = Math.max(1, params.endRow - params.startRow);
        const page = Math.floor(params.startRow / limit) + 1;
        const sortItem = params.sortModel[0];
        const query: TransactionsJournalQuery = {
          page,
          limit,
          sortBy: (sortItem?.colId as TransactionsSortField) ?? 'date',
          sortDir: (sortItem?.sort as 'asc' | 'desc') ?? 'desc',
          type: this.journalTypeFilter() || undefined,
          search: this.journalSearch().trim() || undefined,
        };

        this.transactionsApi.loadTeamTransactionsJournal(teamId, query).subscribe({
          next: (result) => {
            this.journalTotal.set(result.total);
            params.successCallback(result.data, result.total);
          },
          error: () => {
            this.journalTotal.set(0);
            params.failCallback();
          },
        });
      },
    };
  }

  protected onPenaltySelect(penaltyId: number): void {
    const penalty = this.penalties().find((p) => p.id === penaltyId);
    if (penalty) this.applyPenaltyPreset(penalty);
  }

  private applyPenaltyPreset(penalty: Penalty): void {
    this.playerForm.patchValue({ amount: penalty.amount, note: penalty.description, type: 11 });
  }

  private loadPenalties(teamId: number): void {
    this.penaltyApi.loadPenalties(teamId).subscribe({
      next: (penalties) => this.penalties.set(penalties),
      error: () => this.penalties.set([]),
    });
  }

  protected submitPlayerBooking(): void {
    if (!this.canBook() || this.playerForm.invalid || this.saving()) return;
    const value = this.playerForm.getRawValue();
    if (value.playerIds.length === 0) return;

    const amounts = splitAmounts(value.amount, value.playerIds.length, value.total);
    const transactions: CreatePlayerTransaction[] = value.playerIds.map((playerId, index) => ({
      playerId,
      amount: amounts[index],
      type: value.type,
      note: value.note.trim() || null,
      date: this.toIsoDate(value.date),
    }));

    if (value.amount >= HIGH_AMOUNT_CONFIRM_THRESHOLD) {
      this.confirm({
        title: 'Betrag prüfen',
        message: `Die Buchung über ${value.amount.toFixed(2)} € ist ungewöhnlich hoch. Wirklich fortfahren?`,
        confirmLabel: 'Buchen',
      }).subscribe((confirmed) => {
        if (confirmed) this.createPlayerTransactions(transactions);
      });
      return;
    }

    this.createPlayerTransactions(transactions);
  }

  protected submitTeamBooking(): void {
    const team = this.team();
    if (!this.canBook() || !team || this.teamForm.invalid || this.saving()) return;
    const value = this.teamForm.getRawValue();
    const transaction: CreateTeamWalletTransaction = {
      teamId: team.id,
      amount: value.amount,
      type: value.type,
      note: value.note.trim() || null,
      date: this.toIsoDate(value.date),
    };

    const save = () => this.createTeamWalletTransaction(transaction);
    if (value.amount >= HIGH_AMOUNT_CONFIRM_THRESHOLD) {
      this.confirm({
        title: 'Betrag prüfen',
        message: `Die Buchung über ${value.amount.toFixed(2)} € ist ungewöhnlich hoch. Wirklich fortfahren?`,
        confirmLabel: 'Buchen',
      }).subscribe((confirmed) => {
        if (confirmed) save();
      });
      return;
    }
    save();
  }

  protected canReverse(activity: TeamActivity): boolean {
    return (
      this.permissions.canDo(this.team(), 'transactionReverse') &&
      !activity.isTeamWalletTransaction &&
      !activity.note?.startsWith('Stornierung von Buchung #')
    );
  }

  protected reverseBooking(activity: TeamActivity): void {
    if (!this.canReverse(activity) || this.saving()) return;
    this.confirm({
      title: 'Buchung stornieren',
      message: `Die Buchung über ${activity.amount.toFixed(2)} € wirklich stornieren? Die Originalbuchung bleibt sichtbar.`,
      confirmLabel: 'Stornieren',
    }).subscribe((confirmed) => {
      if (!confirmed) return;
      this.saving.set(true);
      this.transactionsApi.reverseTransaction(activity.id).subscribe({
        next: () => this.afterMutation('Buchung wurde storniert.'),
        error: () => this.handleError('Buchung konnte nicht storniert werden.'),
      });
    });
  }

  protected typeLabel(type: string): string {
    return (
      {
        payment: 'Zahlung',
        credit: 'Guthaben',
        fine: 'Strafe',
        levy: 'Umlage',
        fee: 'Gebühr',
        expense: 'Ausgabe',
      }[type] ?? type
    );
  }

  private createPlayerTransactions(transactions: CreatePlayerTransaction[]): void {
    this.saving.set(true);
    this.transactionsApi.createPlayerTransactions(transactions).subscribe({
      next: () => {
        this.playerForm.reset({
          playerIds: [],
          amount: 0,
          type: 11,
          note: '',
          date: this.today(),
          total: false,
        });
        this.afterMutation('Buchung wurde gespeichert.');
      },
      error: () => this.handleError('Buchung konnte nicht gespeichert werden.'),
    });
  }

  private createTeamWalletTransaction(transaction: CreateTeamWalletTransaction): void {
    this.saving.set(true);
    this.transactionsApi.createTeamWalletTransaction(transaction).subscribe({
      next: () => {
        this.teamForm.reset({ amount: 0, type: 14, note: '', date: this.today() });
        this.afterMutation('Teambuchung wurde gespeichert.');
      },
      error: () => this.handleError('Teambuchung konnte nicht gespeichert werden.'),
    });
  }

  private afterMutation(message: string): void {
    this.saving.set(false);
    this.snackBar.open(message, undefined, { duration: 4000 });
    this.teamStore.refreshTeam();
    this.refreshJournal();
  }

  private handleError(message: string): void {
    this.saving.set(false);
    this.snackBar.open(message, undefined, { duration: 5000 });
  }

  private confirm(data: ConfirmDialogData) {
    return this.dialog.open(ConfirmDialog, { data }).afterClosed();
  }

  private today(): string {
    const now = new Date();
    const offset = now.getTimezoneOffset() * 60_000;
    return new Date(now.getTime() - offset).toISOString().slice(0, 10);
  }

  private toIsoDate(date: string): string {
    return new Date(`${date}T12:00:00`).toISOString();
  }
}
