<?php

declare(strict_types=1);

namespace App\Services\Import;

use App\DTOs\Import\BonusRowDto;
use App\DTOs\Import\EmployeeRowDto;
use App\DTOs\Import\ImportResultDto;
use App\DTOs\Import\ReportRowDto;
use App\DTOs\Import\VacationRowDto;
use App\Enums\ImportType;
use App\Models\Bonus;
use App\Models\DisciplinaryReport;
use App\Models\Employee;
use App\Models\Vacation;
use App\Services\Employee\EmployeeService;
use PhpOffice\PhpSpreadsheet\IOFactory;

class ImportService
{
    public function __construct(
        private readonly EmployeeImportTransformer $employeeTransformer,
        private readonly VacationImportTransformer $vacationTransformer,
        private readonly BonusImportTransformer $bonusTransformer,
        private readonly ReportImportTransformer $reportTransformer,
        private readonly EmployeeService $employeeService,
    ) {}

    /**
     * @return array<int, string>
     */
    public function readHeaders(string $path): array
    {
        $sheet = IOFactory::load($path)->getActiveSheet();
        $headers = [];

        foreach ($sheet->getRowIterator(1, 1) as $row) {
            foreach ($row->getCellIterator() as $cell) {
                $value = trim((string) $cell->getValue());

                if ($value !== '') {
                    $headers[] = $value;
                }
            }
        }

        return $headers;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function readRows(string $path, ?int $limit = null): array
    {
        $sheet = IOFactory::load($path)->getActiveSheet();
        $headers = $this->readHeaders($path);
        $rows = [];
        $highestRow = $sheet->getHighestDataRow();

        for ($rowIndex = 2; $rowIndex <= $highestRow; $rowIndex++) {
            $rowData = [];
            $isEmpty = true;

            foreach ($headers as $columnIndex => $header) {
                $columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($columnIndex + 1);
                $value = $sheet->getCell($columnLetter . $rowIndex)->getFormattedValue();
                $rowData[$header] = is_string($value) ? trim($value) : $value;

                if ($rowData[$header] !== null && $rowData[$header] !== '') {
                    $isEmpty = false;
                }
            }

            if (! $isEmpty) {
                $rows[] = $rowData;
            }

            if ($limit !== null && count($rows) >= $limit) {
                break;
            }
        }

        return $rows;
    }

    /**
     * @param  array<string, string|null>  $columnMapping
     * @return array<int, array<string, mixed>>
     */
    public function mapRows(array $rows, array $columnMapping): array
    {
        return array_map(function (array $row) use ($columnMapping): array {
            $mapped = [];

            foreach ($columnMapping as $field => $header) {
                if ($header === null || $header === '') {
                    continue;
                }

                $mapped[$field] = $row[$header] ?? null;
            }

            return $mapped;
        }, $rows);
    }

    /**
     * @param  array<int, array<string, mixed>>  $mappedRows
     * @return array<int, array{row: int, errors: array<int, string>}>
     */
    public function validatePreview(ImportType $type, array $mappedRows, int $limit = 10): array
    {
        $previewRows = array_slice($mappedRows, 0, $limit);
        $results = [];

        foreach ($previewRows as $index => $row) {
            $results[] = [
                'row' => $index + 2,
                'errors' => $this->validateRow($type, $row),
            ];
        }

        return $results;
    }

    /**
     * @param  array<int, array<string, mixed>>  $mappedRows
     */
    public function import(ImportType $type, array $mappedRows, ?int $userId = null): ImportResultDto
    {
        $imported = 0;
        $skipped = 0;
        $failed = 0;
        $errors = [];

        foreach ($mappedRows as $index => $row) {
            $rowNumber = $index + 2;
            $validationErrors = $this->validateRow($type, $row);

            if ($validationErrors !== []) {
                $failed++;
                $errors[] = "Row {$rowNumber}: " . implode(' ', $validationErrors);

                continue;
            }

            if ($this->isDuplicate($type, $row)) {
                $skipped++;

                continue;
            }

            try {
                $this->persistRow($type, $row, $userId);
                $imported++;
            } catch (\Throwable $exception) {
                $failed++;
                $errors[] = "Row {$rowNumber}: {$exception->getMessage()}";
            }
        }

        return new ImportResultDto(
            total: count($mappedRows),
            imported: $imported,
            skipped: $skipped,
            failed: $failed,
            errors: array_slice($errors, 0, 50),
        );
    }

    /**
     * @param  array<string, mixed>  $row
     * @return array<int, string>
     */
    private function validateRow(ImportType $type, array $row): array
    {
        return match ($type) {
            ImportType::Employees => $this->employeeTransformer->validate(EmployeeRowDto::fromMappedRow($row)),
            ImportType::Vacations => $this->vacationTransformer->validate(VacationRowDto::fromMappedRow($row)),
            ImportType::Bonuses => $this->bonusTransformer->validate(BonusRowDto::fromMappedRow($row)),
            ImportType::Reports => $this->reportTransformer->validate(ReportRowDto::fromMappedRow($row)),
        };
    }

    /**
     * @param  array<string, mixed>  $row
     */
    private function isDuplicate(ImportType $type, array $row): bool
    {
        return match ($type) {
            ImportType::Employees => $this->employeeTransformer->isDuplicate(EmployeeRowDto::fromMappedRow($row)),
            ImportType::Vacations => $this->vacationTransformer->isDuplicate(VacationRowDto::fromMappedRow($row)),
            ImportType::Bonuses => $this->bonusTransformer->isDuplicate(BonusRowDto::fromMappedRow($row)),
            ImportType::Reports => $this->reportTransformer->isDuplicate(ReportRowDto::fromMappedRow($row)),
        };
    }

    /**
     * @param  array<string, mixed>  $row
     */
    private function persistRow(ImportType $type, array $row, ?int $userId): void
    {
        match ($type) {
            ImportType::Employees => $this->employeeService->create(
                $this->employeeTransformer->toAttributes(EmployeeRowDto::fromMappedRow($row)),
            ),
            ImportType::Vacations => Vacation::create(
                $this->vacationTransformer->toAttributes(VacationRowDto::fromMappedRow($row)),
            ),
            ImportType::Bonuses => Bonus::create(
                $this->bonusTransformer->toAttributes(BonusRowDto::fromMappedRow($row)),
            ),
            ImportType::Reports => DisciplinaryReport::create(
                $this->reportTransformer->toAttributes(ReportRowDto::fromMappedRow($row), $userId),
            ),
        };
    }
}
