<?php

declare(strict_types=1);

namespace App\Framework\Database\Schema\Comparison;

/**
 * Represents differences between two database tables
 */
final readonly class TableDifference
{
    /**
     * Create a new table difference
     *
     * @param string $tableName The name of the table
     * @param array<string, array<string, mixed>> $missingColumns Columns that exist in the source but not in the target
     * @param array<string, array<string, mixed>> $extraColumns Columns that exist in the target but not in the source
     * @param array<string, array<string, array<string, mixed>>> $modifiedColumns Columns that exist in both but have differences
     * @param array<string, array<string, mixed>> $missingIndexes Indexes that exist in the source but not in the target
     * @param array<string, array<string, mixed>> $extraIndexes Indexes that exist in the target but not in the source
     * @param array<string, array<string, array<string, mixed>>> $modifiedIndexes Indexes that exist in both but have differences
     * @param array<string, array<string, mixed>> $missingForeignKeys Foreign keys that exist in the source but not in the target
     * @param array<string, array<string, mixed>> $extraForeignKeys Foreign keys that exist in the target but not in the source
     * @param array<string, array<string, array<string, mixed>>> $modifiedForeignKeys Foreign keys that exist in both but have differences
     */
    public function __construct(
        public string $tableName,
        public array $missingColumns,
        public array $extraColumns,
        public array $modifiedColumns,
        public array $missingIndexes,
        public array $extraIndexes,
        public array $modifiedIndexes,
        public array $missingForeignKeys,
        public array $extraForeignKeys,
        public array $modifiedForeignKeys
    ) {
    }

    /**
     * Check if there are any differences in the table
     */
    public function hasDifferences(): bool
    {
        return ! empty($this->missingColumns) || ! empty($this->extraColumns) || ! empty($this->modifiedColumns) ||
               ! empty($this->missingIndexes) || ! empty($this->extraIndexes) || ! empty($this->modifiedIndexes) ||
               ! empty($this->missingForeignKeys) || ! empty($this->extraForeignKeys) || ! empty($this->modifiedForeignKeys);
    }

    /**
     * Get a summary of the differences
     *
     * @return array<string, int> Summary of the differences
     */
    public function getSummary(): array
    {
        return [
            'missing_columns' => count($this->missingColumns),
            'extra_columns' => count($this->extraColumns),
            'modified_columns' => count($this->modifiedColumns),
            'missing_indexes' => count($this->missingIndexes),
            'extra_indexes' => count($this->extraIndexes),
            'modified_indexes' => count($this->modifiedIndexes),
            'missing_foreign_keys' => count($this->missingForeignKeys),
            'extra_foreign_keys' => count($this->extraForeignKeys),
            'modified_foreign_keys' => count($this->modifiedForeignKeys),
        ];
    }

    /**
     * Get a detailed description of the differences
     */
    public function getDescription(): string
    {
        $description = [];

        $description[] = "Table: {$this->tableName}";
        $description[] = "";

        if (! $this->hasDifferences()) {
            $description[] = "No differences found.";

            return implode("\n", $description);
        }

        if (! empty($this->missingColumns)) {
            $description[] = "Missing Columns:";
            foreach ($this->missingColumns as $columnName => $columnDef) {
                $type = $columnDef['type'] ?? 'unknown';
                $nullable = $columnDef['nullable'] ? 'NULL' : 'NOT NULL';
                $default = isset($columnDef['default']) ? "DEFAULT {$columnDef['default']}" : '';
                $description[] = "  - {$columnName} {$type} {$nullable} {$default}";
            }
            $description[] = "";
        }

        if (! empty($this->extraColumns)) {
            $description[] = "Extra Columns:";
            foreach ($this->extraColumns as $columnName => $columnDef) {
                $type = $columnDef['type'] ?? 'unknown';
                $nullable = $columnDef['nullable'] ? 'NULL' : 'NOT NULL';
                $default = isset($columnDef['default']) ? "DEFAULT {$columnDef['default']}" : '';
                $description[] = "  - {$columnName} {$type} {$nullable} {$default}";
            }
            $description[] = "";
        }

        if (! empty($this->modifiedColumns)) {
            $description[] = "Modified Columns:";
            foreach ($this->modifiedColumns as $columnName => $columnDiff) {
                $sourceType = $columnDiff['source']['type'] ?? 'unknown';
                $targetType = $columnDiff['target']['type'] ?? 'unknown';
                $sourceNullable = $columnDiff['source']['nullable'] ? 'NULL' : 'NOT NULL';
                $targetNullable = $columnDiff['target']['nullable'] ? 'NULL' : 'NOT NULL';
                $sourceDefault = isset($columnDiff['source']['default']) ? "DEFAULT {$columnDiff['source']['default']}" : '';
                $targetDefault = isset($columnDiff['target']['default']) ? "DEFAULT {$columnDiff['target']['default']}" : '';

                $description[] = "  - {$columnName}:";
                $description[] = "      Source: {$sourceType} {$sourceNullable} {$sourceDefault}";
                $description[] = "      Target: {$targetType} {$targetNullable} {$targetDefault}";
            }
            $description[] = "";
        }

        if (! empty($this->missingIndexes)) {
            $description[] = "Missing Indexes:";
            foreach ($this->missingIndexes as $indexName => $indexDef) {
                $type = $indexDef['type'] ?? 'INDEX';
                $unique = $indexDef['unique'] ? 'UNIQUE' : '';
                $columns = implode(', ', $indexDef['columns'] ?? []);
                $description[] = "  - {$indexName} {$unique} {$type} ({$columns})";
            }
            $description[] = "";
        }

        if (! empty($this->extraIndexes)) {
            $description[] = "Extra Indexes:";
            foreach ($this->extraIndexes as $indexName => $indexDef) {
                $type = $indexDef['type'] ?? 'INDEX';
                $unique = $indexDef['unique'] ? 'UNIQUE' : '';
                $columns = implode(', ', $indexDef['columns'] ?? []);
                $description[] = "  - {$indexName} {$unique} {$type} ({$columns})";
            }
            $description[] = "";
        }

        if (! empty($this->modifiedIndexes)) {
            $description[] = "Modified Indexes:";
            foreach ($this->modifiedIndexes as $indexName => $indexDiff) {
                $sourceType = $indexDiff['source']['type'] ?? 'INDEX';
                $targetType = $indexDiff['target']['type'] ?? 'INDEX';
                $sourceUnique = $indexDiff['source']['unique'] ? 'UNIQUE' : '';
                $targetUnique = $indexDiff['target']['unique'] ? 'UNIQUE' : '';
                $sourceColumns = implode(', ', $indexDiff['source']['columns'] ?? []);
                $targetColumns = implode(', ', $indexDiff['target']['columns'] ?? []);

                $description[] = "  - {$indexName}:";
                $description[] = "      Source: {$sourceUnique} {$sourceType} ({$sourceColumns})";
                $description[] = "      Target: {$targetUnique} {$targetType} ({$targetColumns})";
            }
            $description[] = "";
        }

        if (! empty($this->missingForeignKeys)) {
            $description[] = "Missing Foreign Keys:";
            foreach ($this->missingForeignKeys as $fkName => $fkDef) {
                $columns = implode(', ', $fkDef['columns'] ?? []);
                $refTable = $fkDef['referenced_table'] ?? '';
                $refColumns = implode(', ', $fkDef['referenced_columns'] ?? []);
                $updateRule = $fkDef['update_rule'] ?? 'NO ACTION';
                $deleteRule = $fkDef['delete_rule'] ?? 'NO ACTION';

                $description[] = "  - {$fkName}: ({$columns}) REFERENCES {$refTable} ({$refColumns})";
                $description[] = "      ON UPDATE {$updateRule} ON DELETE {$deleteRule}";
            }
            $description[] = "";
        }

        if (! empty($this->extraForeignKeys)) {
            $description[] = "Extra Foreign Keys:";
            foreach ($this->extraForeignKeys as $fkName => $fkDef) {
                $columns = implode(', ', $fkDef['columns'] ?? []);
                $refTable = $fkDef['referenced_table'] ?? '';
                $refColumns = implode(', ', $fkDef['referenced_columns'] ?? []);
                $updateRule = $fkDef['update_rule'] ?? 'NO ACTION';
                $deleteRule = $fkDef['delete_rule'] ?? 'NO ACTION';

                $description[] = "  - {$fkName}: ({$columns}) REFERENCES {$refTable} ({$refColumns})";
                $description[] = "      ON UPDATE {$updateRule} ON DELETE {$deleteRule}";
            }
            $description[] = "";
        }

        if (! empty($this->modifiedForeignKeys)) {
            $description[] = "Modified Foreign Keys:";
            foreach ($this->modifiedForeignKeys as $fkName => $fkDiff) {
                $sourceColumns = implode(', ', $fkDiff['source']['columns'] ?? []);
                $sourceRefTable = $fkDiff['source']['referenced_table'] ?? '';
                $sourceRefColumns = implode(', ', $fkDiff['source']['referenced_columns'] ?? []);
                $sourceUpdateRule = $fkDiff['source']['update_rule'] ?? 'NO ACTION';
                $sourceDeleteRule = $fkDiff['source']['delete_rule'] ?? 'NO ACTION';

                $targetColumns = implode(', ', $fkDiff['target']['columns'] ?? []);
                $targetRefTable = $fkDiff['target']['referenced_table'] ?? '';
                $targetRefColumns = implode(', ', $fkDiff['target']['referenced_columns'] ?? []);
                $targetUpdateRule = $fkDiff['target']['update_rule'] ?? 'NO ACTION';
                $targetDeleteRule = $fkDiff['target']['delete_rule'] ?? 'NO ACTION';

                $description[] = "  - {$fkName}:";
                $description[] = "      Source: ({$sourceColumns}) REFERENCES {$sourceRefTable} ({$sourceRefColumns})";
                $description[] = "              ON UPDATE {$sourceUpdateRule} ON DELETE {$sourceDeleteRule}";
                $description[] = "      Target: ({$targetColumns}) REFERENCES {$targetRefTable} ({$targetRefColumns})";
                $description[] = "              ON UPDATE {$targetUpdateRule} ON DELETE {$targetDeleteRule}";
            }
            $description[] = "";
        }

        return implode("\n", $description);
    }
}
