<?php

namespace Laminas\Db\Metadata\Source;

use Laminas\Db\Adapter\Adapter;

use function implode;
use function strtoupper;

/**
 * Metadata source for Oracle
 */
class OracleMetadata extends AbstractSource
{
    /** @var array */
    protected $constraintTypeMap = [
        'C' => 'CHECK',
        'P' => 'PRIMARY KEY',
        'R' => 'FOREIGN_KEY',
    ];

    /**
     * {@inheritdoc}
     *
     * @see \Laminas\Db\Metadata\Source\AbstractSource::loadColumnData()
     */
    protected function loadColumnData($table, $schema)
    {
        if (isset($this->data['columns'][$schema][$table])) {
            return;
        }

        $isColumns = [
            'COLUMN_ID',
            'COLUMN_NAME',
            'DATA_DEFAULT',
            'NULLABLE',
            'DATA_TYPE',
            'DATA_LENGTH',
            'DATA_PRECISION',
            'DATA_SCALE',
        ];

        $this->prepareDataHierarchy('columns', $schema, $table);
        $parameters = [
            ':ownername' => $schema,
            ':tablename' => $table,
        ];

        $sql = 'SELECT ' . implode(', ', $isColumns)
             . ' FROM all_tab_columns'
             . ' WHERE owner = :ownername AND table_name = :tablename';

        $result  = $this->adapter->query($sql)->execute($parameters);
        $columns = [];

        foreach ($result as $row) {
            $columns[$row['COLUMN_NAME']] = [
                'ordinal_position'         => $row['COLUMN_ID'],
                'column_default'           => $row['DATA_DEFAULT'],
                'is_nullable'              => 'Y' === $row['NULLABLE'],
                'data_type'                => $row['DATA_TYPE'],
                'character_maximum_length' => $row['DATA_LENGTH'],
                'character_octet_length'   => null,
                'numeric_precision'        => $row['DATA_PRECISION'],
                'numeric_scale'            => $row['DATA_SCALE'],
                'numeric_unsigned'         => false,
                'erratas'                  => [],
            ];
        }

        $this->data['columns'][$schema][$table] = $columns;
        return $this;
    }

    /**
     * Constraint type
     *
     * @param string $type
     * @return string
     */
    protected function getConstraintType($type)
    {
        if (isset($this->constraintTypeMap[$type])) {
            return $this->constraintTypeMap[$type];
        }

        return $type;
    }

    /**
     * {@inheritdoc}
     *
     * @see \Laminas\Db\Metadata\Source\AbstractSource::loadConstraintData()
     */
    protected function loadConstraintData($table, $schema)
    {
        // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps
        if (isset($this->data['constraints'][$schema][$table])) {
            return;
        }

        $this->prepareDataHierarchy('constraints', $schema, $table);
        $sql = '
            SELECT
                ac.owner,
                ac.constraint_name,
                ac.constraint_type,
                ac.search_condition check_clause,
                ac.table_name,
                ac.delete_rule,
                cc1.column_name,
                cc2.table_name as ref_table,
                cc2.column_name as ref_column,
                cc2.owner as ref_owner
            FROM all_constraints ac
            INNER JOIN all_cons_columns cc1
                ON cc1.constraint_name = ac.constraint_name
            LEFT JOIN all_cons_columns cc2
                ON cc2.constraint_name = ac.r_constraint_name
                AND cc2.position = cc1.position

            WHERE
                ac.owner = :ownername AND ac.table_name = :tablename

            ORDER BY ac.constraint_name
        ';

        $parameters = [
            ':ownername' => $schema,
            ':tablename' => $table,
        ];

        $results     = $this->adapter->query($sql)->execute($parameters);
        $isFK        = false;
        $name        = null;
        $constraints = [];

        foreach ($results as $row) {
            if ($row['CONSTRAINT_NAME'] !== $name) {
                $name               = $row['CONSTRAINT_NAME'];
                $constraints[$name] = [
                    'constraint_name' => $name,
                    'constraint_type' => $this->getConstraintType($row['CONSTRAINT_TYPE']),
                    'table_name'      => $row['TABLE_NAME'],
                ];

                if ('C' === $row['CONSTRAINT_TYPE']) {
                    $constraints[$name]['CHECK_CLAUSE'] = $row['CHECK_CLAUSE'];
                    continue;
                }

                $constraints[$name]['columns'] = [];

                $isFK = 'R' === $row['CONSTRAINT_TYPE'];
                if ($isFK) {
                    $constraints[$name]['referenced_table_schema'] = $row['REF_OWNER'];
                    $constraints[$name]['referenced_table_name']   = $row['REF_TABLE'];
                    $constraints[$name]['referenced_columns']      = [];
                    $constraints[$name]['match_option']            = 'NONE';
                    $constraints[$name]['update_rule']             = null;
                    $constraints[$name]['delete_rule']             = $row['DELETE_RULE'];
                }
            }

            $constraints[$name]['columns'][] = $row['COLUMN_NAME'];
            if ($isFK) {
                $constraints[$name]['referenced_columns'][] = $row['REF_COLUMN'];
            }
        }

        $this->data['constraints'][$schema][$table] = $constraints;

        return $this;
        // phpcs:enable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps
    }

    /**
     * {@inheritdoc}
     *
     * @see \Laminas\Db\Metadata\Source\AbstractSource::loadSchemaData()
     */
    protected function loadSchemaData()
    {
        if (isset($this->data['schemas'])) {
            return;
        }

        $this->prepareDataHierarchy('schemas');
        $sql     = 'SELECT USERNAME FROM ALL_USERS';
        $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);

        $schemas = [];
        foreach ($results->toArray() as $row) {
            $schemas[] = $row['USERNAME'];
        }

        $this->data['schemas'] = $schemas;
    }

    /**
     * {@inheritdoc}
     *
     * @see \Laminas\Db\Metadata\Source\AbstractSource::loadTableNameData()
     */
    protected function loadTableNameData($schema)
    {
        if (isset($this->data['table_names'][$schema])) {
            return $this;
        }

        $this->prepareDataHierarchy('table_names', $schema);
        $tables = [];

        // Tables
        $bind   = [':OWNER' => strtoupper($schema)];
        $result = $this->adapter->query('SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER=:OWNER')->execute($bind);

        foreach ($result as $row) {
            $tables[$row['TABLE_NAME']] = [
                'table_type'      => 'BASE TABLE',
                'view_definition' => null,
                'check_option'    => null,
                'is_updatable'    => false,
            ];
        }

        // Views
        $result = $this->adapter->query('SELECT VIEW_NAME, TEXT FROM ALL_VIEWS WHERE OWNER=:OWNER', $bind);
        foreach ($result as $row) {
            $tables[$row['VIEW_NAME']] = [
                'table_type'      => 'VIEW',
                'view_definition' => null,
                'check_option'    => 'NONE',
                'is_updatable'    => false,
            ];
        }

        $this->data['table_names'][$schema] = $tables;
        return $this;
    }

    /**
     * FIXME: load trigger data
     *
     * {@inheritdoc}
     *
     * @see \Laminas\Db\Metadata\Source\AbstractSource::loadTriggerData()
     */
    protected function loadTriggerData($schema)
    {
        if (isset($this->data['triggers'][$schema])) {
            return;
        }

        $this->prepareDataHierarchy('triggers', $schema);
    }
}
