<?php

declare(strict_types=1);

namespace App\Repositories;

final class ReferenceDataRepository extends BaseRepository
{
    private const DEFAULT_PLANNING_TYPES = [
        ['system_key' => 'private_block', 'name' => 'Privat', 'color' => '#6B7280', 'default_duration' => 60, 'category' => 'private', 'sort_order' => 900],
        ['system_key' => 'work_vehicle_care', 'name' => 'Fahrzeugpflege', 'color' => '#0E5D80', 'default_duration' => 60, 'category' => 'work', 'sort_order' => 910],
        ['system_key' => 'work_office', 'name' => 'Buero', 'color' => '#8B5A2B', 'default_duration' => 60, 'category' => 'work', 'sort_order' => 920],
        ['system_key' => 'work_transfer_drive', 'name' => 'Wechselfahrt', 'color' => '#3B7A57', 'default_duration' => 45, 'category' => 'work', 'sort_order' => 930],
        ['system_key' => 'work_other', 'name' => 'Sonstige Taetigkeit', 'color' => '#7C3AED', 'default_duration' => 60, 'category' => 'work', 'sort_order' => 940],
        ['system_key' => 'theory_a', 'name' => 'Theorie A', 'color' => '#C2410C', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 950],
        ['system_key' => 'theory_b', 'name' => 'Theorie B', 'color' => '#A74826', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 960],
        ['system_key' => 'theory_c', 'name' => 'Theorie C', 'color' => '#B45309', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 970],
        ['system_key' => 'theory_d', 'name' => 'Theorie D', 'color' => '#BE123C', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 980],
        ['system_key' => 'theory_t', 'name' => 'Theorie T', 'color' => '#047857', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 990],
        ['system_key' => 'theory_bkf', 'name' => 'Theorie BKF', 'color' => '#1D4ED8', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 1000],
    ];

    public function requirementKeyForLessonType(array $lessonType): string
    {
        $key = trim((string) ($lessonType['requirement_key'] ?? ''));
        return $key !== '' ? $key : 'lesson_type_' . (int) $lessonType['id'];
    }

    public function lessonTypes(int $tenantId): array
    {
        $this->ensureDefaultPlanningTypes($tenantId);
        $statement = $this->db->prepare('SELECT * FROM lesson_types WHERE tenant_id = :tenant_id ORDER BY sort_order, name');
        $statement->execute(['tenant_id' => $tenantId]);
        $lessonTypes = $statement->fetchAll();
        $visibilityMap = $this->lessonTypeVisibilityMap($tenantId);

        foreach ($lessonTypes as &$lessonType) {
            $lessonType['visible_template_ids'] = $visibilityMap[(int) $lessonType['id']] ?? [];
            $lessonType['effective_requirement_key'] = $this->requirementKeyForLessonType($lessonType);
        }

        return $lessonTypes;
    }

    public function createLessonType(int $tenantId, array $data): array
    {
        $timestamp = $this->now();
        $statement = $this->db->prepare(
            'INSERT INTO lesson_types
            (tenant_id, name, color, default_duration, category, requirement_key, is_billable, is_counted, is_rest_relevant, fixed_duration, sort_order, created_at, updated_at)
            VALUES
            (:tenant_id, :name, :color, :default_duration, :category, :requirement_key, :is_billable, :is_counted, :is_rest_relevant, :fixed_duration, :sort_order, :created_at, :updated_at)'
        );
        $statement->execute([
            'tenant_id' => $tenantId,
            'name' => $data['name'],
            'color' => $data['color'],
            'default_duration' => (int) $data['default_duration'],
            'category' => $data['category'],
            'requirement_key' => $data['requirement_key'] ?: null,
            'is_billable' => !empty($data['is_billable']) ? 1 : 0,
            'is_counted' => !empty($data['is_counted']) ? 1 : 0,
            'is_rest_relevant' => !empty($data['is_rest_relevant']) ? 1 : 0,
            'fixed_duration' => !empty($data['fixed_duration']) ? 1 : 0,
            'sort_order' => (int) ($data['sort_order'] ?? 0),
            'created_at' => $timestamp,
            'updated_at' => $timestamp,
        ]);
        $lessonTypeId = (int) $this->lastInsertId();
        $this->replaceLessonTypeVisibility($tenantId, $lessonTypeId, $data['visible_template_ids'] ?? null);

        return $this->findLessonType($tenantId, $lessonTypeId);
    }

    public function updateLessonType(int $tenantId, int $id, array $data): ?array
    {
        $statement = $this->db->prepare(
            'UPDATE lesson_types
             SET name = :name, color = :color, default_duration = :default_duration, category = :category,
                 requirement_key = :requirement_key, is_billable = :is_billable, is_counted = :is_counted,
                 is_rest_relevant = :is_rest_relevant, fixed_duration = :fixed_duration, sort_order = :sort_order,
                 updated_at = :updated_at
             WHERE id = :id AND tenant_id = :tenant_id'
        );
        $statement->execute([
            'id' => $id,
            'tenant_id' => $tenantId,
            'name' => $data['name'],
            'color' => $data['color'],
            'default_duration' => (int) $data['default_duration'],
            'category' => $data['category'],
            'requirement_key' => $data['requirement_key'] ?: null,
            'is_billable' => !empty($data['is_billable']) ? 1 : 0,
            'is_counted' => !empty($data['is_counted']) ? 1 : 0,
            'is_rest_relevant' => !empty($data['is_rest_relevant']) ? 1 : 0,
            'fixed_duration' => !empty($data['fixed_duration']) ? 1 : 0,
            'sort_order' => (int) ($data['sort_order'] ?? 0),
            'updated_at' => $this->now(),
        ]);

        $this->replaceLessonTypeVisibility($tenantId, $id, $data['visible_template_ids'] ?? null);

        return $this->findLessonType($tenantId, $id);
    }

    public function deleteLessonType(int $tenantId, int $id): bool
    {
        $lessonType = $this->findLessonTypeRecord($tenantId, $id);
        if ($lessonType === null) {
            return false;
        }

        if (($lessonType['system_key'] ?? null) === 'private_block') {
            throw new \RuntimeException('Der feste Privat-Baustein kann nicht geloescht werden.');
        }

        $usage = $this->db->prepare('SELECT COUNT(*) AS count FROM appointments WHERE tenant_id = :tenant_id AND lesson_type_id = :lesson_type_id');
        $usage->execute(['tenant_id' => $tenantId, 'lesson_type_id' => $id]);
        if ((int) ($usage->fetch()['count'] ?? 0) > 0) {
            throw new \RuntimeException('Diese Stundenart wird bereits in Terminen verwendet und kann nicht geloescht werden.');
        }

        $this->db->beginTransaction();
        try {
            $visibility = $this->db->prepare('DELETE FROM lesson_type_template_visibility WHERE lesson_type_id = :lesson_type_id');
            $visibility->execute(['lesson_type_id' => $id]);

            $delete = $this->db->prepare('DELETE FROM lesson_types WHERE tenant_id = :tenant_id AND id = :id');
            $delete->execute(['tenant_id' => $tenantId, 'id' => $id]);

            $this->db->commit();
            return true;
        } catch (\Throwable $exception) {
            $this->db->rollBack();
            throw $exception;
        }
    }

    public function reorderLessonTypes(int $tenantId, array $lessonTypeIds): array
    {
        $allowedIds = array_map(
            static fn (array $lessonType): int => (int) $lessonType['id'],
            $this->lessonTypes($tenantId)
        );

        $statement = $this->db->prepare(
            'UPDATE lesson_types
             SET sort_order = :sort_order, updated_at = :updated_at
             WHERE tenant_id = :tenant_id AND id = :id'
        );

        $sortOrder = 1;
        foreach ($lessonTypeIds as $lessonTypeId) {
            $lessonTypeId = (int) $lessonTypeId;
            if (!in_array($lessonTypeId, $allowedIds, true)) {
                continue;
            }

            $statement->execute([
                'sort_order' => $sortOrder,
                'updated_at' => $this->now(),
                'tenant_id' => $tenantId,
                'id' => $lessonTypeId,
            ]);
            $sortOrder++;
        }

        return $this->lessonTypes($tenantId);
    }

    public function findLessonType(int $tenantId, int $id): ?array
    {
        $statement = $this->db->prepare('SELECT * FROM lesson_types WHERE tenant_id = :tenant_id AND id = :id');
        $statement->execute(['tenant_id' => $tenantId, 'id' => $id]);
        $lessonType = $statement->fetch() ?: null;
        if ($lessonType === null) {
            return null;
        }

        $lessonType['visible_template_ids'] = $this->visibleTemplateIdsForLessonType($tenantId, $id);
        $lessonType['effective_requirement_key'] = $this->requirementKeyForLessonType($lessonType);

        return $lessonType;
    }

    public function templates(int $tenantId): array
    {
        $statement = $this->db->prepare(
            'SELECT * FROM license_class_templates WHERE tenant_id = :tenant_id ORDER BY name'
        );
        $statement->execute(['tenant_id' => $tenantId]);
        $templates = $statement->fetchAll();

        foreach ($templates as &$template) {
            $template['requirements'] = $this->templateRequirements((int) $template['id']);
            $template['lesson_type_ids'] = $this->lessonTypeIdsForTemplate((int) $template['id']);
        }

        return $templates;
    }

    public function templateRequirements(int $templateId): array
    {
        $statement = $this->db->prepare(
            'SELECT * FROM license_template_requirements WHERE template_id = :template_id ORDER BY sort_order, phase_label, label'
        );
        $statement->execute(['template_id' => $templateId]);

        return $statement->fetchAll();
    }

    public function createTemplate(int $tenantId, array $data): array
    {
        $timestamp = $this->now();
        $statement = $this->db->prepare(
            'INSERT INTO license_class_templates
            (tenant_id, code, name, is_combination, created_at, updated_at)
            VALUES (:tenant_id, :code, :name, :is_combination, :created_at, :updated_at)'
        );
        $statement->execute([
            'tenant_id' => $tenantId,
            'code' => $data['code'],
            'name' => $data['name'],
            'is_combination' => !empty($data['is_combination']) ? 1 : 0,
            'created_at' => $timestamp,
            'updated_at' => $timestamp,
        ]);
        $templateId = (int) $this->lastInsertId();

        $this->replaceTemplateRequirements($templateId, $data['requirements'] ?? []);
        $this->attachTemplateToExistingStudentLessonTypes($tenantId, $templateId);

        return $this->findTemplate($tenantId, $templateId);
    }

    public function updateTemplate(int $tenantId, int $id, array $data): ?array
    {
        $statement = $this->db->prepare(
            'UPDATE license_class_templates
             SET code = :code, name = :name, is_combination = :is_combination, updated_at = :updated_at
             WHERE id = :id AND tenant_id = :tenant_id'
        );
        $statement->execute([
            'id' => $id,
            'tenant_id' => $tenantId,
            'code' => $data['code'],
            'name' => $data['name'],
            'is_combination' => !empty($data['is_combination']) ? 1 : 0,
            'updated_at' => $this->now(),
        ]);

        $this->replaceTemplateRequirements($id, $data['requirements'] ?? []);

        return $this->findTemplate($tenantId, $id);
    }

    public function updateLessonTypeMatrix(int $tenantId, array $rows): array
    {
        $templates = $this->templates($tenantId);
        $templateIds = array_map(static fn (array $template): int => (int) $template['id'], $templates);
        $allowedLessonTypeIds = array_map(
            static fn (array $lessonType): int => (int) $lessonType['id'],
            array_filter($this->lessonTypes($tenantId), static fn (array $lessonType): bool => $lessonType['category'] === 'student')
        );

        $delete = $this->db->prepare(
            'DELETE FROM lesson_type_template_visibility
             WHERE template_id = :template_id
             AND lesson_type_id IN (
                SELECT id FROM lesson_types WHERE tenant_id = :tenant_id AND category = \'student\'
             )'
        );
        $insert = $this->db->prepare(
            'INSERT OR IGNORE INTO lesson_type_template_visibility (lesson_type_id, template_id)
             VALUES (:lesson_type_id, :template_id)'
        );

        foreach ($rows as $row) {
            $templateId = (int) ($row['template_id'] ?? 0);
            if (!in_array($templateId, $templateIds, true)) {
                continue;
            }

            $delete->execute([
                'template_id' => $templateId,
                'tenant_id' => $tenantId,
            ]);

            foreach ((array) ($row['lesson_type_ids'] ?? []) as $lessonTypeId) {
                $lessonTypeId = (int) $lessonTypeId;
                if (!in_array($lessonTypeId, $allowedLessonTypeIds, true)) {
                    continue;
                }

                $insert->execute([
                    'lesson_type_id' => $lessonTypeId,
                    'template_id' => $templateId,
                ]);
            }
        }

        return $this->templates($tenantId);
    }

    public function isLessonTypeVisibleForTemplate(int $tenantId, int $lessonTypeId, int $templateId): bool
    {
        $lessonType = $this->findLessonType($tenantId, $lessonTypeId);
        if ($lessonType === null) {
            return false;
        }

        if ($lessonType['category'] !== 'student') {
            return true;
        }

        return in_array($templateId, array_map('intval', $lessonType['visible_template_ids'] ?? []), true);
    }

    private function replaceTemplateRequirements(int $templateId, array $requirements): void
    {
        $delete = $this->db->prepare('DELETE FROM license_template_requirements WHERE template_id = :template_id');
        $delete->execute(['template_id' => $templateId]);

        $insert = $this->db->prepare(
            'INSERT INTO license_template_requirements
            (template_id, requirement_key, label, required_units, sort_order, phase_label)
            VALUES (:template_id, :requirement_key, :label, :required_units, :sort_order, :phase_label)'
        );

        foreach ($requirements as $index => $requirement) {
            if (($requirement['label'] ?? '') === '') {
                continue;
            }

            $insert->execute([
                'template_id' => $templateId,
                'requirement_key' => $requirement['requirement_key'],
                'label' => $requirement['label'],
                'required_units' => (int) $requirement['required_units'],
                'sort_order' => (int) ($requirement['sort_order'] ?? $index),
                'phase_label' => trim((string) ($requirement['phase_label'] ?? '')),
            ]);
        }
    }

    public function findTemplate(int $tenantId, int $id): ?array
    {
        $statement = $this->db->prepare(
            'SELECT * FROM license_class_templates WHERE tenant_id = :tenant_id AND id = :id'
        );
        $statement->execute(['tenant_id' => $tenantId, 'id' => $id]);
        $template = $statement->fetch() ?: null;

        if ($template !== null) {
            $template['requirements'] = $this->templateRequirements($id);
            $template['lesson_type_ids'] = $this->lessonTypeIdsForTemplate($id);
        }

        return $template;
    }

    private function replaceLessonTypeVisibility(int $tenantId, int $lessonTypeId, ?array $visibleTemplateIds): void
    {
        $lessonType = $this->findLessonTypeRecord($tenantId, $lessonTypeId);
        if ($lessonType === null) {
            return;
        }

        $delete = $this->db->prepare('DELETE FROM lesson_type_template_visibility WHERE lesson_type_id = :lesson_type_id');
        $delete->execute(['lesson_type_id' => $lessonTypeId]);

        if ($lessonType['category'] !== 'student') {
            return;
        }

        $templateIds = $visibleTemplateIds ?? $this->templateIdsForTenant($tenantId);
        $templateIds = array_values(array_unique(array_map('intval', $templateIds)));
        if ($templateIds === []) {
            return;
        }

        $allowedTemplateIds = $this->templateIdsForTenant($tenantId);
        $insert = $this->db->prepare(
            'INSERT OR IGNORE INTO lesson_type_template_visibility (lesson_type_id, template_id)
             VALUES (:lesson_type_id, :template_id)'
        );

        foreach ($templateIds as $templateId) {
            if (!in_array($templateId, $allowedTemplateIds, true)) {
                continue;
            }

            $insert->execute([
                'lesson_type_id' => $lessonTypeId,
                'template_id' => $templateId,
            ]);
        }
    }

    private function lessonTypeVisibilityMap(int $tenantId): array
    {
        $statement = $this->db->prepare(
            'SELECT lt.id AS lesson_type_id, vis.template_id
             FROM lesson_types lt
             LEFT JOIN lesson_type_template_visibility vis ON vis.lesson_type_id = lt.id
             WHERE lt.tenant_id = :tenant_id AND lt.category = \'student\'
             ORDER BY vis.template_id'
        );
        $statement->execute(['tenant_id' => $tenantId]);

        $map = [];
        foreach ($statement->fetchAll() as $row) {
            $lessonTypeId = (int) $row['lesson_type_id'];
            $map[$lessonTypeId] ??= [];
            if ($row['template_id'] !== null) {
                $map[$lessonTypeId][] = (int) $row['template_id'];
            }
        }

        return $map;
    }

    private function visibleTemplateIdsForLessonType(int $tenantId, int $lessonTypeId): array
    {
        $lessonType = $this->findLessonTypeRecord($tenantId, $lessonTypeId);
        if ($lessonType === null || $lessonType['category'] !== 'student') {
            return [];
        }

        $statement = $this->db->prepare(
            'SELECT template_id FROM lesson_type_template_visibility
             WHERE lesson_type_id = :lesson_type_id
             ORDER BY template_id'
        );
        $statement->execute(['lesson_type_id' => $lessonTypeId]);

        return array_map(
            static fn (array $row): int => (int) $row['template_id'],
            $statement->fetchAll()
        );
    }

    private function lessonTypeIdsForTemplate(int $templateId): array
    {
        $statement = $this->db->prepare(
            'SELECT lesson_type_id FROM lesson_type_template_visibility
             WHERE template_id = :template_id
             ORDER BY lesson_type_id'
        );
        $statement->execute(['template_id' => $templateId]);

        return array_map(
            static fn (array $row): int => (int) $row['lesson_type_id'],
            $statement->fetchAll()
        );
    }

    private function templateIdsForTenant(int $tenantId): array
    {
        $statement = $this->db->prepare('SELECT id FROM license_class_templates WHERE tenant_id = :tenant_id ORDER BY id');
        $statement->execute(['tenant_id' => $tenantId]);

        return array_map(
            static fn (array $row): int => (int) $row['id'],
            $statement->fetchAll()
        );
    }

    private function attachTemplateToExistingStudentLessonTypes(int $tenantId, int $templateId): void
    {
        $statement = $this->db->prepare(
            'INSERT OR IGNORE INTO lesson_type_template_visibility (lesson_type_id, template_id)
             SELECT id, :template_id FROM lesson_types
             WHERE tenant_id = :tenant_id AND category = \'student\''
        );
        $statement->execute([
            'template_id' => $templateId,
            'tenant_id' => $tenantId,
        ]);
    }

    private function findLessonTypeRecord(int $tenantId, int $id): ?array
    {
        $statement = $this->db->prepare('SELECT * FROM lesson_types WHERE tenant_id = :tenant_id AND id = :id');
        $statement->execute(['tenant_id' => $tenantId, 'id' => $id]);

        return $statement->fetch() ?: null;
    }

    private function ensureDefaultPlanningTypes(int $tenantId): void
    {
        $statement = $this->db->prepare(
            'INSERT OR IGNORE INTO lesson_types
             (tenant_id, name, color, default_duration, category, requirement_key, is_billable, is_counted, is_rest_relevant, fixed_duration, sort_order, system_key, created_at, updated_at)
             VALUES
             (:tenant_id, :name, :color, :default_duration, :category, NULL, 0, 0, :is_rest_relevant, 0, :sort_order, :system_key, :created_at, :updated_at)'
        );

        $timestamp = $this->now();
        foreach (self::DEFAULT_PLANNING_TYPES as $row) {
            $statement->execute([
                'tenant_id' => $tenantId,
                'name' => $row['name'],
                'color' => $row['color'],
                'default_duration' => $row['default_duration'],
                'category' => $row['category'],
                'is_rest_relevant' => $row['category'] === 'private' ? 0 : 1,
                'sort_order' => $row['sort_order'],
                'system_key' => $row['system_key'],
                'created_at' => $timestamp,
                'updated_at' => $timestamp,
            ]);
        }
    }
}
