<?php

declare(strict_types=1);

class Ocorrencia extends Model
{
    /** @var array<string,bool> */
    private static $columnCache = [];

    public function criar(array $dados): int
    {
        $sql = "INSERT INTO ocorrencias (
                    protocolo, categoria_id, descricao, latitude, longitude,
                    endereco_referencia, cidadao_nome, cidadao_telefone, cidadao_email,
                    anonima, status, imagem, criado_em
                ) VALUES (
                    :protocolo, :categoria_id, :descricao, :latitude, :longitude,
                    :endereco_referencia, :cidadao_nome, :cidadao_telefone, :cidadao_email,
                    :anonima, 'ABERTA', :imagem, NOW()
                )";

        $stmt = $this->db->prepare($sql);
        $stmt->execute($dados);

        $id = (int)$this->db->lastInsertId();
        $this->registrarHistorico($id, null, null, 'ABERTA', 'Ocorrência aberta pelo cidadão.');

        return $id;
    }

    public function listar(): array
    {
        $sql = "SELECT o.*, c.nome AS categoria_nome, " . $this->categoryColorSql() . " AS categoria_cor, s.nome AS setor_nome, " . $this->slaDiasSql() . " AS sla_dias
                FROM ocorrencias o
                INNER JOIN categorias c ON c.id = o.categoria_id
                LEFT JOIN setores s ON s.id = o.setor_id
                ORDER BY o.id DESC";

        $items = $this->db->query($sql)->fetchAll();
        return $this->appendSlaMeta($items);
    }

    public function listarComFiltros(array $filtros = []): array
    {
        $sql = "SELECT o.*, c.nome AS categoria_nome, " . $this->categoryColorSql() . " AS categoria_cor, s.nome AS setor_nome, " . $this->slaDaysRemainingSql() . " AS sla_dias
                FROM ocorrencias o
                INNER JOIN categorias c ON c.id = o.categoria_id
                LEFT JOIN setores s ON s.id = o.setor_id
                WHERE 1=1";

        $params = [];

        if (!empty($filtros['protocolo'])) {
            $sql .= " AND o.protocolo LIKE :protocolo";
            $params['protocolo'] = '%' . $filtros['protocolo'] . '%';
        }

        if (!empty($filtros['status'])) {
            $sql .= " AND o.status = :status";
            $params['status'] = $filtros['status'];
        }

        if (!empty($filtros['categoria_id'])) {
            $sql .= " AND o.categoria_id = :categoria_id";
            $params['categoria_id'] = (int)$filtros['categoria_id'];
        }

        if (!empty($filtros['setor_id'])) {
            $sql .= " AND o.setor_id = :setor_id";
            $params['setor_id'] = (int)$filtros['setor_id'];
        }

        if (!empty($filtros['data_inicio'])) {
            $sql .= " AND DATE(o.criado_em) >= :data_inicio";
            $params['data_inicio'] = $filtros['data_inicio'];
        }

        if (!empty($filtros['data_fim'])) {
            $sql .= " AND DATE(o.criado_em) <= :data_fim";
            $params['data_fim'] = $filtros['data_fim'];
        }

        $sql .= " ORDER BY o.id DESC";

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);

        return $this->appendSlaMeta($stmt->fetchAll());
    }

    public function listarRecentes(int $limite = 5): array
    {
        $limite = max(1, $limite);
        $sql = "SELECT o.id, o.protocolo, o.status, o.criado_em, c.nome AS categoria_nome, " . $this->categoryColorSql() . " AS categoria_cor, " . $this->slaDaysRemainingSql() . " AS sla_dias
                FROM ocorrencias o
                INNER JOIN categorias c ON c.id = o.categoria_id
                ORDER BY o.id DESC
                LIMIT {$limite}";
        $items = $this->db->query($sql)->fetchAll();
        return $this->appendSlaMeta($items);
    }

    public function listarParaMapa(int $limite = 200): array
    {
        $limite = max(1, $limite);
        $sql = "SELECT o.id, o.protocolo, o.latitude, o.longitude, o.status, o.endereco_referencia,
                       c.nome AS categoria_nome, " . $this->categoryColorSql() . " AS categoria_cor
                FROM ocorrencias o
                INNER JOIN categorias c ON c.id = o.categoria_id
                WHERE o.latitude IS NOT NULL
                  AND o.longitude IS NOT NULL
                  AND o.status IN " . $this->mapStatusesSql() . "
                ORDER BY o.id DESC
                LIMIT {$limite}";
        return $this->db->query($sql)->fetchAll();
    }

    public function buscarPorId(int $id): ?array
    {
        $sql = "SELECT o.*, c.nome AS categoria_nome, " . $this->categoryColorSql() . " AS categoria_cor, s.nome AS setor_nome, " . $this->slaDaysRemainingSql() . " AS sla_dias
                FROM ocorrencias o
                INNER JOIN categorias c ON c.id = o.categoria_id
                LEFT JOIN setores s ON s.id = o.setor_id
                WHERE o.id = :id
                LIMIT 1";

        $stmt = $this->db->prepare($sql);
        $stmt->execute(['id' => $id]);

        $row = $stmt->fetch();
        if (!$row) {
            return null;
        }
        $items = $this->appendSlaMeta([$row]);
        return $items[0] ?? null;
    }

    public function buscarPorProtocolo(string $protocolo): ?array
    {
        $sql = "SELECT o.*, c.nome AS categoria_nome, " . $this->categoryColorSql() . " AS categoria_cor, s.nome AS setor_nome, " . $this->slaDaysRemainingSql() . " AS sla_dias
                FROM ocorrencias o
                INNER JOIN categorias c ON c.id = o.categoria_id
                LEFT JOIN setores s ON s.id = o.setor_id
                WHERE o.protocolo = :protocolo
                LIMIT 1";

        $stmt = $this->db->prepare($sql);
        $stmt->execute(['protocolo' => $protocolo]);

        $row = $stmt->fetch();
        if (!$row) {
            return null;
        }
        $items = $this->appendSlaMeta([$row]);
        return $items[0] ?? null;
    }

    public function contarPorStatus(): array
    {
        $rows = $this->db->query("SELECT status, COUNT(*) AS total FROM ocorrencias GROUP BY status")->fetchAll();
        $base = [
            'ABERTA' => 0,
            'RECEBIDA' => 0,
            'EM_ANALISE' => 0,
            'ENCAMINHADA' => 0,
            'EM_ATENDIMENTO' => 0,
            'CONCLUIDA' => 0,
            'NAO_PROCEDE' => 0,
            'DUPLICADA' => 0,
        ];
        foreach ($rows as $row) {
            $base[$row['status']] = (int)$row['total'];
        }
        $base['TOTAL'] = array_sum($base);
        return $base;
    }

    public function contarSla(): array
    {
        $items = $this->appendSlaMeta(
            $this->db->query("SELECT o.id, o.status, o.criado_em, c.nome AS categoria_nome, " . $this->slaDaysRemainingSql() . " AS sla_dias
                              FROM ocorrencias o
                              INNER JOIN categorias c ON c.id = o.categoria_id")->fetchAll()
        );

        $summary = [
            'NO_PRAZO' => 0,
            'ATRASADAS' => 0,
            'FINALIZADAS' => 0,
        ];

        foreach ($items as $item) {
            if ($this->isClosedStatus((string)$item['status'])) {
                $summary['FINALIZADAS']++;
            } elseif (!empty($item['sla_atrasado'])) {
                $summary['ATRASADAS']++;
            } else {
                $summary['NO_PRAZO']++;
            }
        }

        return $summary;
    }

    public function atualizarStatus(int $id, string $status, ?int $usuarioId = null): void
    {
        $atual = $this->buscarPorId($id);
        if (!$atual) {
            throw new RuntimeException('Ocorrência não encontrada.');
        }

        $sql = "UPDATE ocorrencias
                SET status = :status,
                    atualizado_em = NOW(),
                    concluido_em = CASE WHEN :status = 'CONCLUIDA' THEN NOW() ELSE concluido_em END
                WHERE id = :id";

        $stmt = $this->db->prepare($sql);
        $stmt->execute([
            'status' => $status,
            'id' => $id,
        ]);

        $this->registrarHistorico($id, $usuarioId, $atual['status'], $status, 'Status atualizado no painel administrativo.');
    }

    public function atualizarSetor(int $id, ?int $setorId, ?int $usuarioId = null): void
    {
        $atual = $this->buscarPorId($id);
        if (!$atual) {
            throw new RuntimeException('Ocorrência não encontrada.');
        }

        $stmt = $this->db->prepare("UPDATE ocorrencias SET setor_id = :setor_id, atualizado_em = NOW() WHERE id = :id");
        $stmt->bindValue(':id', $id, PDO::PARAM_INT);
        if ($setorId === null || $setorId <= 0) {
            $stmt->bindValue(':setor_id', null, PDO::PARAM_NULL);
        } else {
            $stmt->bindValue(':setor_id', $setorId, PDO::PARAM_INT);
        }
        $stmt->execute();

        $descricao = $setorId ? 'Ocorrência vinculada a um setor.' : 'Vínculo de setor removido.';
        $this->registrarHistorico($id, $usuarioId, null, $atual['status'], $descricao);
    }

    public function historico(int $id): array
    {
        $sql = "SELECT h.*, u.nome AS usuario_nome
                FROM ocorrencia_historico h
                LEFT JOIN usuarios u ON u.id = h.usuario_id
                WHERE h.ocorrencia_id = :id
                ORDER BY h.id DESC";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['id' => $id]);
        return $stmt->fetchAll();
    }

    private function appendSlaMeta(array $items): array
    {
        foreach ($items as &$item) {
            $slaDias = isset($item['sla_dias']) ? (int)$item['sla_dias'] : 3;
            if ($slaDias <= 0) {
                $slaDias = 3;
            }

            $item['sla_dias'] = $slaDias;
            $item['sla_status'] = 'NO PRAZO';
            $item['sla_atrasado'] = 0;
            $item['sla_limite'] = null;

            if (empty($item['criado_em'])) {
                continue;
            }

            $criadoTs = strtotime((string)$item['criado_em']);
            if ($criadoTs === false) {
                continue;
            }

            $limiteTs = strtotime('+' . $slaDias . ' days', $criadoTs);
            $item['sla_limite'] = date('Y-m-d H:i:s', $limiteTs);

            if (!$this->isClosedStatus((string)($item['status'] ?? '')) && time() > $limiteTs) {
                $item['sla_status'] = 'ATRASADO';
                $item['sla_atrasado'] = 1;
            }
        }

        return $items;
    }

    private function isClosedStatus(string $status): bool
    {
        return in_array($status, ['CONCLUIDA', 'NAO_PROCEDE', 'DUPLICADA'], true);
    }

    private function categoryColorSql(): string
    {
        return "COALESCE(NULLIF(c.cor, ''), CASE c.nome
                    WHEN 'Buraco na via' THEN '#d9480f'
                    WHEN 'Iluminação pública' THEN '#f59f00'
                    WHEN 'Entulho em via pública' THEN '#495057'
                    WHEN 'Mato alto' THEN '#2b8a3e'
                    WHEN 'Bueiro entupido' THEN '#1864ab'
                    ELSE '#5f3dc4'
                END)";
    }

    private function slaDaysRemainingSql(): string
    {
        if ($this->tableHasColumn('categorias', 'sla_dias')) {
            return "COALESCE(NULLIF(c.sla_dias, 0), " . $this->slaFallbackCaseSql() . ")";
        }

        return $this->slaFallbackCaseSql();
    }

    private function slaFallbackCaseSql(): string
    {
        return "(CASE c.nome
                    WHEN 'Buraco na via' THEN 5
                    WHEN 'Iluminação pública' THEN 2
                    WHEN 'Entulho em via pública' THEN 4
                    WHEN 'Mato alto' THEN 4
                    WHEN 'Bueiro entupido' THEN 3
                    ELSE 3
                END)";
    }

    private function tableHasColumn(string $table, string $column): bool
    {
        $key = $table . '.' . $column;
        if (isset(self::$columnCache[$key])) {
            return self::$columnCache[$key];
        }

        try {
            $stmt = $this->db->prepare("SHOW COLUMNS FROM `{$table}` LIKE :column");
            $stmt->execute(['column' => $column]);
            self::$columnCache[$key] = (bool)$stmt->fetch();
        } catch (Throwable $e) {
            self::$columnCache[$key] = false;
        }

        return self::$columnCache[$key];
    }

    private function mapStatusesSql(): string
    {
        return "('ABERTA','RECEBIDA','EM_ANALISE','ENCAMINHADA','EM_ATENDIMENTO')";
    }

    private function registrarHistorico(int $ocorrenciaId, ?int $usuarioId, ?string $statusAnterior, string $statusNovo, ?string $observacao): void
    {
        $stmt = $this->db->prepare("INSERT INTO ocorrencia_historico (ocorrencia_id, usuario_id, status_anterior, status_novo, observacao, criado_em)
                                   VALUES (:ocorrencia_id, :usuario_id, :status_anterior, :status_novo, :observacao, NOW())");
        $stmt->execute([
            'ocorrencia_id' => $ocorrenciaId,
            'usuario_id' => $usuarioId,
            'status_anterior' => $statusAnterior,
            'status_novo' => $statusNovo,
            'observacao' => $observacao,
        ]);
    }
}
