<?php

use Mpdf\Mpdf;

class Agro_APIs_VSD_Vistoria
{
    private $vistoria = [];
    private $usuario = [];
    private $db = null;
    private $idLaudoFinal = null;
    private $vlAreaVistoriadaTotal = null;
    private $dsCoordenadaVistoria = null;
    private $dtInicioVistoria = null;
    private $idPropostaVigente = null;
    private $idTipoLaudoFinal = null;
    private $idFuncionarioPeritoPrincipal = null;
    private $possuiItemConcluidoAutomaticamente = false;

    const ID_LF003 = 157;
    const ID_LF034 = 170;
    const TIPOS_DOCUMENTOS = [
        'FOTO_ACOMPANHANTE_DOC' => 1,
        'FOTO_DOC_FRENTE' => 2,
        'FOTO_DOC_VERSO' => 3,
        'FOTO_AUTORIZACAO' => 4,
        'FOTO_LOCAL_OU_GPS' => 5,
        'VIDEO_ACOMPANHANTE' => 6,
        'FOTO_LOCAL_INICIO' => 7,
        'FOTO_LOCAL_ZONEAMENTO' => 8,
        'FOTO_LOCAL_DISPENSA_VISTORIA' => 9,
        'FOTO_LOCAL_NEGACAO_ITEM' => 10,
        'FOTO_TELA_GPS' => 11,
        'FOTO_VISTORIA_ITEM' => 12,
        'FOTO_GPS_MECANIZADA_INICIO' => 13,
        'FOTO_GPS_MECANIZADA_TERMINO' => 14,
        'FOTO_INICIO_MANUAL' => 15,
        'FOTO_GPS_MANUAL' => 16,
        'FOTO_AMOSTRA_MANUAL' => 17,
        'FOTO_PESAGEM_AMOSTRA_MANUAL' => 18,
        'FOTO_DISPENSA' => 19,
        'FOTO_AMOSTRA_MECANIZADA' => 20
    ];

    const MOTIVO_AREA_COLHIDA = 1;
    const MOTIVO_SEGURADO_DISPENSA_VISTORIA = 2;
    const MOTIVO_AREA_ERRADICADA = 3;
    const MOTIVO_NAO_SEMEADURA_PODA_TALHAO = 4;
    const MOTIVO_NAO_EMERGENCIA_PLANTAS = 5;
    const MOTIVO_AREA_NAO_ATINGIDA_NAO_SINISTRADA = 7;
    const MOTIVO_DATA_OCORRENCIA_EVENTO_ESTA_FORA_PEREODO_COBERTURA = 12;
    const MOTIVO_VISTORIA_NAO_CONCLUIDA = 15;
    const MOTIVO_ABANDONO_AREA = 16;
    const MOTIVO_PRIMEIRO_ANO_PLANTIO = 17;
    const MOTIVO_RESTRICOES_TECNICAS = 18;
    const MOTIVO_MANEJO_INADEQUADO_LAVOURA_POMAR = 19;
    const MOTIVO_LAMINA_DAGUA = 20;
    const MOTIVO_INUNDACAO_TOTAL = 21;
    const MOTIVO_CONDUTA_INADEQUADA_ATIPICA = 22;
    const MOTIVO_OCORRENCIA_EVENTO_NAO_COBERTO = 23;
    const MOTIVO_AUSENCIA_IRRIGACAO = 24;
    const MOTIVO_CULTURA_AVALIADA_DIVERGENTE_CULTURA_CONTRATADA = 25;

    /**
     * Salva a vistoria.
     *
     * @param array $vistoria
     * @param array $usuario
     * @param Zend_Db_Adapter_Pdo_Pgsql &$db
     */
    public function salvar(array $vistoria, array $usuario, Zend_Db_Adapter_Pdo_Pgsql &$db): void
    {
        $this->vistoria = $vistoria;
        $this->usuario = $usuario;
        $this->db = $db;
        $this->idLaudoFinal = $this->getIdLaudoFinalByVistoria($this->vistoria['id_vistoria']);
        $this->idPropostaVigente = $this->getPropostaVigenteByLaudoFinal();
        $this->idTipoLaudoFinal = $this->getIdTipoLaudoFinalByProposta($this->idPropostaVigente);

        //$this->debug();
        $this->validarVistoriaJaImportada();
        $this->salvarLaudosFinaisItensSegurados();
        $this->salvarCroqui();
        $this->salvarLaudosFinaisAgronomosAgro();
        $this->salvarDocumentosAcompanhante();
        $this->removerLaudosFinaisFuncionarios();
        $this->salvarLaudosFinaisAssinaturas();
        $this->salvarFotosVistoria();
        $this->salvarLaudoFinal();
    }

    /**
     * Remove todos os dados incluídos para conseguir executar várias vezes a mesma vistoria
     */
    private function debug(): void
    {
        $this->db->update('sinistro.laudos_finais', ["fl_transmitido" => "false"], "id = '{$this->idLaudoFinal}'");
        $this->db->delete('regulacao.lf003_itens_segurados', "id_laudo_final_item_segurado IN (SELECT id FROM sinistro.laudos_finais_itens_segurados where id_laudo_final = {$this->idLaudoFinal})");
        $this->db->delete('regulacao.lf034_itens_segurados', "id_laudo_final_item_segurado IN (SELECT id FROM sinistro.laudos_finais_itens_segurados where id_laudo_final = {$this->idLaudoFinal})");
        $this->db->delete('regulacao.lf003_itens_segurados_amostras', "id_laudo_final_item_segurado IN (SELECT id FROM sinistro.laudos_finais_itens_segurados where id_laudo_final = {$this->idLaudoFinal})");
        $this->db->delete('regulacao.lf034_itens_segurados_amostras', "id_laudo_final_item_segurado IN (SELECT id FROM sinistro.laudos_finais_itens_segurados where id_laudo_final = {$this->idLaudoFinal})");
        $this->db->delete('regulacao.croquis_talhoes', "id_croqui IN (SELECT id FROM regulacao.croquis where id_laudo = {$this->idLaudoFinal})");
        $this->db->delete('regulacao.croquis', "id_laudo = {$this->idLaudoFinal}");
        $this->db->delete('sinistro.laudos_finais_agronomos_agro', "id_laudo_final = {$this->idLaudoFinal}");
        $this->db->delete('sinistro.laudos_finais_assinaturas', "id_laudo_final = {$this->idLaudoFinal}");
        $this->db->delete('sinistro.laudos_finais_itens_segurados_arquivos', "id_item_laudo_final IN (SELECT id FROM sinistro.laudos_finais_itens_segurados where id_laudo_final = {$this->idLaudoFinal})");
        $this->db->delete('sinistro.laudos_finais_arquivos', "id_laudo_final = {$this->idLaudoFinal}");
        $this->db->delete('sinistro.laudos_finais_itens_segurados', "id_laudo_final = {$this->idLaudoFinal}");
        $this->removerLaudosFinaisFuncionarios();
    }

    /**
     * Retorna o ID do laudo final
     * 
     * @return int|null
     */
    public function getIdLaudoFinal(): ?int
    {
        return $this->idLaudoFinal;
    }

    /**
     * Retorna o ID do tipo de laudo final
     */
    public function getIdTipoLaudoFinal(): int
    {
        return $this->idTipoLaudoFinal;
    }

    /**
     * Remove os peritos que foram selecionados no laudo final antes de incluir o que veio pelo JSON para evitar a quebra de chave duplicada
     */
    private function removerLaudosFinaisFuncionarios(): void
    {
        $this->db->delete('sinistro.laudos_finais_funcionarios', "id_laudo_final = {$this->idLaudoFinal}");
    }

    private function validarVistoriaJaImportada(): void
    {
        $query = $this->db->select();
        $query->from('sinistro.laudos_finais', 'fl_transmitido');
        $query->where('id = ?', $this->idLaudoFinal);
        
        $rs = $this->db->fetchRow($query);

        if (empty($rs)) {
            throw new Exception("Não foi encontrado laudo final {$this->idLaudoFinal}.");
        }

        if ($rs['fl_transmitido']) {
            throw new Exception("A vistoria {$this->vistoria['id_vistoria']} já possui um laudo digitado.");
        }
    }

    /**
     * Busca o ID do laudo final criado no momento do agendamento da vistoria.
     *
     * @param int $idVistoria
     * @return int
     */
    private function getIdLaudoFinalByVistoria(int $idVistoria): int
    {
        $queryVistoriaLaudosFinais = $this->db->select();
        $queryVistoriaLaudosFinais->from('sistema.vistorias_laudos_finais', '*');
        $queryVistoriaLaudosFinais->where('id_vistoria = ?', $idVistoria);
        
        $vistoriaLaudosFinais = $this->db->fetchRow($queryVistoriaLaudosFinais);

        if (empty($vistoriaLaudosFinais)) {
            throw new Exception("Não foi encontrado laudo final para a vistoria {$idVistoria}.");
        }

        return $vistoriaLaudosFinais['id_laudo_final'];
    }

    /**
     * Retorna o id do tipo de laudo final para a proposta informada
     *
     * @param int $idProposta
     * @return int
     */
    private function getIdTipoLaudoFinalByProposta(int $idProposta): int
    {
        $query = $this->db->select();
        $query->from(['ppc' => 'produto.produtos_coberturas'], ['id_tipo_laudo_final']);
        $query->join(['pr' => 'seguro.propostas'], 'pr.id_produto = ppc.id_produto', ['pr.id']);
        $query->join(['pce' => 'produto.coberturas_eventos'], 'pce.id_cobertura = ppc.id_cobertura', null);
        $query->join(['sae' => 'sinistro.avisos_eventos'], 'sae.id_evento = pce.id_evento', null);
        $query->join(['sa' => 'sinistro.avisos'], 'sa.id_proposta = pr.id AND sa.id = sae.id_aviso', null);
        $query->where('ppc.fl_cobertura_principal = ?', true);
        $query->where('pr.id = ?', $idProposta);
        $query->group(['pr.id', 'ppc.id_tipo_laudo_final']);

        $rs = $this->db->fetchRow($query);

        if (empty($rs)) {
            throw new Exception("Não foi encontrado o tipo de laudo final para a vistoria {$this->vistoria['id_vistoria']}.");
        }

        return $rs['id_tipo_laudo_final'];
    }

    /**
     * Retorna o id da proposta vigente pelo laudo final
     *
     * @return int
     */
    private function getPropostaVigenteByLaudoFinal(): int
    {
        $subQueryProposta = $this->db->select();
        $subQueryProposta->from(['proc' => 'sinistro.processos'], 'id_proposta');
        $subQueryProposta->join(['lf' => 'sinistro.laudos_finais'], 'lf.id_processo = proc.id', false);
        $subQueryProposta->where('lf.id = ?', $this->idLaudoFinal);

        $subQueryEndosso = $this->db->select();
        $subQueryEndosso->from(['pe2'=>'seguro.propostas_endosso'], 'id_endosso');
        $subQueryEndosso->where('pe2.id_proposta IN (?)', $subQueryProposta);

        $query = $this->db->select();
        $query->from(['pe'=>'seguro.propostas_endosso'], 'id_proposta');
        $query->where('pe.fl_proposta_vigente = ?', true);
        $query->where('pe.id_endosso IN (?)', $subQueryEndosso);

        return $this->db->fetchOne($query);
    }


	/**
	 * Retorna o id do status vinculado ao motivo
	 *
	 * @param int|null $id
	 * @return int
	 */
	private function getStatusIdByMotivo(?int $id): int
	{
		if (empty($id)) {
			return $this->getIdStatus('FINALIZADO', 'ITENS_VISTORIAS_FINAIS');
		}

		$select = $this->db->select();
		$select->from(array("mqnv" => "sinistro.motivos_quadra_nao_vistoriada"), 'id_status');
		$select->where("mqnv.id = ?", $id);

        $rs = $this->db->fetchRow($select);

        if (empty($rs)) {
            throw new Exception("Não foi encontrado o status para o motivo {$id}.");
        }

        return $rs['id_status'];
	}

    /**
     * Salva os itens do laudo final.
     * Tabela: sinistro.laudos_finais_itens_segurados
     */
    private function salvarLaudosFinaisItensSegurados(): void
    {
        $this->vlAreaVistoriadaTotal = 0.00;

        if (empty($this->vistoria['itens'])) {
            return;
        }

        foreach ($this->vistoria['itens'] as $item) {

            $this->vlAreaVistoriadaTotal += $item['vl_area_vistoriada'];

            if (empty($this->dtInicioVistoria) && !empty($item['dt_inicio_vistoria'])) {
                $this->dtInicioVistoria = $item['dt_inicio_vistoria'];
            }

            $dtCriacao = date('Y-m-d H:i:s');
            $flNaoVistoriada = !empty($item['id_motivo']) ? 1 : 0;
            $flAssinado = ($item['fl_acordo_preenchimentos']) ? 1 : 0;
            $vlAreaRestaAvaliar = $item['vl_area_declarada'] - $item['vl_area_vistoriada'];
            $vlAreaRestaAvaliar = ($vlAreaRestaAvaliar <= 0) ? 0 : $vlAreaRestaAvaliar;
            
            if (!empty($item['id_motivo'])) {
                //Caso haja um motivo de dispensa, buscamos o status de acordo com o motivo informado
                $idStatus = $this->getStatusIdByMotivo($item['id_motivo']);
            } else if ($vlAreaRestaAvaliar == 0 && $flAssinado) {
                //Caso toda a área tenha sido avaliada e o item tenha sido assinado, definimos como FINALIZADO
                $idStatus = $this->getIdStatus('FINALIZADO', 'ITENS_VISTORIAS_FINAIS');
            } else {
                $idStatus = $this->getIdStatus('AGUARDANDO_VISTORIA', 'ITENS_VISTORIAS_FINAIS');
            }
            
            $this->possuiItemConcluidoAutomaticamente = $item['id_motivo'] == self::MOTIVO_VISTORIA_NAO_CONCLUIDA;
            $item['fl_vistoria_parcial'] = $idStatus == $this->getIdStatus('AGUARDANDO_VISTORIA', 'ITENS_VISTORIAS_FINAIS');

            $this->validaRendimentoFinalPercentualColhido($item);

            $valuesLaudosFinaisItensSegurados = [
                'id_usuario_criacao' =>  $this->usuario['id'],
                'dt_criacao' => $dtCriacao,
                'id_laudo_final' => $this->idLaudoFinal,
                'id_item_segurado' => $item['id_item_segurado'],
                'fl_nao_vistoriada' => $flNaoVistoriada,
                'id_motivo_quadra_nao_vistoriada' => $item['id_motivo'],
                'dt_vistoria' => $item['dt_inicio_vistoria'],
                'fl_assinatura_regulador' => 'true',
                'fl_assinatura_segurado' => $flAssinado,
                'vl_percent_colhido' => $item['vl_area_colhidos_antes_vistoria'],
                'ds_coordenadas_geograficas' => $this->getCoordenadaInicio($item),
                'vl_rendimento_final_lavoura' => $item['vl_rendimento_final'],
                //'ds_nome_laudo_fisico' => '', // Adicionar depois de criado o laudo
                'id_item_segurado_origem' => $item['id_item_origem_replicacao'],
                'id_status' => $idStatus, //Criado na tarefa 2310271211
                'fl_assinado' => $flAssinado, //Criado na tarefa 2310271211
                'dt_fim_vistoria' => $item['dt_fim_vistoria'], //Criado nesta tarefa (2310111037)
                'vl_area_medicao' => $item['vl_area_medicao'], //Criado nesta tarefa (2310111037)
                'vl_area_vistoriada' => $item['vl_area_vistoriada'], //Criado nesta tarefa (2310111037)
                'vl_area_resta_avaliar' => $vlAreaRestaAvaliar, //Criado nesta tarefa (2310111037)
                'dt_nova_vistoria' => $item['dt_nova_vistoria'], //Criado nesta tarefa (2310111037)
                'vl_altitude' => $item['vl_altitude'], //Criado nesta tarefa (2310111037)
                'vl_area_declarada' => $item['vl_area_declarada'], //Criado nesta tarefa (2310111037)
                'id_variedade' => $item['id_variedade'], //Criado nesta tarefa (2310111037)
                'id_tipo_solo' => $item['id_tipo_solo'], //Criado nesta tarefa (2310111037)
                'id_sistema_plantio' => $item['id_sistema_plantio'], //Criado nesta tarefa (2310111037)
                'dt_inicio_plantio' => $item['dt_inicio_plantio'], //Criado nesta tarefa (2310111037)
                'dt_termino_plantio' => $item['dt_termino_plantio'], //Criado nesta tarefa (2310111037)
                'fl_zoneamento_valido' => $item['fl_zoneamento_valido'] ? 1 : 0, //Criado nesta tarefa (2310111037)
                'id_tipo_laudo_final' => $this->idTipoLaudoFinal, //Criado nesta tarefa (2310111037)
                'ds_tipo_amostragem' => $item['ds_tipo_amostragem'], //Criado nesta tarefa (2310111037)
                'fl_vistoria_parcial' => $item['fl_vistoria_parcial'] ? 1 : 0, //Criado nesta tarefa (2310111037)
            ];

            $rs = $this->db->insert('sinistro.laudos_finais_itens_segurados', $valuesLaudosFinaisItensSegurados);
            
            if (empty($rs)) {
                throw new Exception("Erro ao incluir o item {$item['id_item_segurado']} do laudo final da vistoria {$this->vistoria['id_vistoria']}.");
            }

            $idLaudoFinalItemSegurado = $this->db->lastInsertId();

            $this->salvarLaudosFinaisItensSeguradosArquivos($item, $idLaudoFinalItemSegurado);
            $this->salvarItensSeguradosAmostras($item, $idLaudoFinalItemSegurado);
            $this->salvarItensSeguradosLaudos($item, $idLaudoFinalItemSegurado);
        }
    }

    /**
     * Valida se o há motivo de dispensa pro item e se o valor de rendimento está zerado
     * e alteramos o rendimento final da lavoura igual ao rendimento por tonelada do item segurado.
     * Além disso, define que o percentual colhido foi 100.
     *
     * @param array $item
     */
    private function validaRendimentoFinalPercentualColhido(array &$item): void
    {
        $motivos = [
            self::MOTIVO_AREA_COLHIDA,
            self::MOTIVO_SEGURADO_DISPENSA_VISTORIA,
            self::MOTIVO_AREA_ERRADICADA,
            self::MOTIVO_NAO_SEMEADURA_PODA_TALHAO,
            self::MOTIVO_NAO_EMERGENCIA_PLANTAS,
            self::MOTIVO_AREA_NAO_ATINGIDA_NAO_SINISTRADA,
            self::MOTIVO_DATA_OCORRENCIA_EVENTO_ESTA_FORA_PEREODO_COBERTURA,
            self::MOTIVO_ABANDONO_AREA,
            self::MOTIVO_PRIMEIRO_ANO_PLANTIO,
            self::MOTIVO_RESTRICOES_TECNICAS,
            self::MOTIVO_MANEJO_INADEQUADO_LAVOURA_POMAR,
            self::MOTIVO_LAMINA_DAGUA,
            self::MOTIVO_INUNDACAO_TOTAL,
            self::MOTIVO_CONDUTA_INADEQUADA_ATIPICA,
            self::MOTIVO_OCORRENCIA_EVENTO_NAO_COBERTO,
            self::MOTIVO_AUSENCIA_IRRIGACAO,
            self::MOTIVO_CULTURA_AVALIADA_DIVERGENTE_CULTURA_CONTRATADA,
        ];

        if (in_array($item['id_motivo'], $motivos) && $item['vl_rendimento_final'] == 0) {
            $item['vl_rendimento_final'] = $this->getRendimentoToneladaItem($item['id_item_segurado']);
            $item['vl_area_colhidos_antes_vistoria'] = 100;
        }
    }

    /**
     * Busca o valor de rendimento tonelada do item segurado da proposta
     *
     * @param int $idItemSegurado
     * @return float
     */
    private function getRendimentoToneladaItem(int $idItemSegurado): float
    {
        $query = $this->db->select();
        $query->from('seguro.itens_segurados', 'vl_rendimento_tonelada');
        $query->where('id = ?', $idItemSegurado);
        $rs = $this->db->fetchRow($query);

        if (empty($rs)) {
            throw new Exception("Não possível buscar o valor de produtividade esperada do item {$idItemSegurado}.");
        }

        return !empty($rs['vl_rendimento_tonelada']) ? $rs['vl_rendimento_tonelada'] : 0;
    }

    private function getDataInicioVistoria(): ?string
    {
        if (!empty($this->dtInicioVistoria)) {
            return $this->dtInicioVistoria;
        }

        if (empty($this->vistoria['itens'])) {
            return null;
        }

        foreach ($this->vistoria['itens'] as $item) {
            
            if (!empty($item['dt_inicio_vistoria'])) {
                $this->dtInicioVistoria = $item['dt_inicio_vistoria'];
                break;
            }
        }

        $qtdHoras = $this->getHorasEncerramentoVistoriaAutomatico();
        $dtAtual = new DateTime();
        $dtAtual->modify("-{$qtdHoras} hours");
        $this->dtInicioVistoria = $dtAtual->format('Y-m-d H:i:s');

        return $this->dtInicioVistoria;
    }

    /**
     * Retorna a configuração de quantidade de horas que uma vistoria deve ser encerrada
     * automaticamente após ter sido iniciada.
     *
     * @return int|null
     */
    private function getHorasEncerramentoVistoriaAutomatico(): int
    {
        $query = $this->db->select();
        $query->from('sistema.configuracao_sistema', 'ds_valor');
        $query->where('ds_nome = ?', 'VSD_ENCERRAMENTO_VISTORIA_AUTOMATICO');
        
        $rs = $this->db->fetchRow($query);

        if (empty($rs)) {
            throw new Exception("Não possível localizar a configuração de horas do encerramento automático da vistoria.");
        }

        return !empty($rs['ds_valor']) ? (int) $rs['ds_valor'] : 0;
    }

    /**
     * Retorna a coordenada da foto do local de início da vistoria do item.
     * 
     * @param array $item
     * @return string
     */
    private function getCoordenadaInicio(array $item): string
    {
        $coordenada = '';

        foreach ($item['fotos_item'] as $foto) {
            if ($foto['ds_tipo_arquivo'] == 'FOTO_LOCAL_INICIO' && !empty($foto['ds_coordenada'])) {

                $latitude = $this->formatarDecimaisCoordenadas($foto['ds_coordenada']['latitude']);
                $longitude = $this->formatarDecimaisCoordenadas($foto['ds_coordenada']['longitude']);
                $coordenada = $latitude ." , ". $longitude;
                break;
            }
        }

        return $coordenada;
    }

    /**
     * Salva os croquis dos itens.
     * Tabelas: regulacao.croquis e regulacao.croquis_talhoes
     */
    private function salvarCroqui(): void
    {
        if (empty($this->vistoria['croqui'])) {
            return;
        }

        foreach ($this->vistoria['croqui']['itens'] as $croqui) {
            $valuesCroqui = [
                'id_laudo' => $this->idLaudoFinal,
                'ds_referencia_regulacao' => '',
                'ds_nome_arquivo' => '',
                'fl_vigente' => 'true',
            ];

            $rsCroqui = $this->db->insert('regulacao.croquis', $valuesCroqui);
            
            if (empty($rsCroqui)) {
                throw new Exception("Erro ao incluir o croqui do item {$croqui['id_item_segurado']} do laudo final da vistoria {$this->vistoria['id_vistoria']}.");
            }

            $idCroqui = $this->db->lastInsertId();

            $valuesCroquiTalhoes = [
                'id_croqui' => $idCroqui,
                'ds_nome' => $croqui['ds_nome_item'],
                'fl_area' => $croqui['vl_area'],
                'ds_coordenadas' => json_encode($croqui['ds_coordenadas']),
                'ds_item_segurado' => "{$croqui['id_item_segurado']}",
            ];
            
            $rsCroquiTalhoes = $this->db->insert('regulacao.croquis_talhoes', $valuesCroquiTalhoes);

            if (empty($rsCroquiTalhoes)) {
                throw new Exception("Erro ao incluir os talhões do croqui do item {$croqui['id_item_segurado']} do laudo final da vistoria {$this->vistoria['id_vistoria']}.");
            }
        }
    }

    /**
     * Salva as fotos do item da vistoria.
     * Tabela: sinistro.laudos_finais_itens_segurados_arquivos
     *
     * @param array $item
     * @param int $idLaudoFinalItemSegurado
     */
    private function salvarLaudosFinaisItensSeguradosArquivos(array $item, int $idLaudoFinalItemSegurado): void
    {
        foreach ($item['fotos_item'] as $foto) {
            $dtCriacao = date('Y-m-d H:i:s');
            $dsNomeArquivo = explode("/",  $foto['ds_nome_arquivo']); 
            $dsNomeArquivo = end($dsNomeArquivo);

            $values = [
                'id_usuario_criacao' => $this->usuario['id'],
                'dt_criacao' => $dtCriacao,
                'id_item_laudo_final' => $idLaudoFinalItemSegurado,
                'ds_nome_arquivo' => $dsNomeArquivo,
                'id_tipo_arquivo_laudo_final' => self::TIPOS_DOCUMENTOS[$foto['ds_tipo_arquivo']], //Criado nesta tarefa (2310111037)
                "ds_coordenada" => json_encode($foto['ds_coordenada']), //Criado nesta tarefa (2310111037)
                "md5" => $foto['md5'], //Criado nesta tarefa (2310111037)
            ];
           
            $rs = $this->db->insert('sinistro.laudos_finais_itens_segurados_arquivos', $values);
            
            if (empty($rs)) {
                throw new Exception("Erro ao incluir o item {$item['id_item_segurado']} do laudo final da vistoria {$this->vistoria['id_vistoria']}.");
            }
        }
    }

    /**
     * Salva as amostras de um item dependendo do tipo de cobertura.
     * Tabelas: regulacao.lf003_itens_segurados_amostras ou regulacao.lf034_itens_segurados_amostras
     *
     * @param array $item
     * @param int $idLaudoFinalItemSegurado
     */
    private function salvarItensSeguradosAmostras(array $item, int $idLaudoFinalItemSegurado): void
    {
        $tabelaAmostras = ($this->idTipoLaudoFinal == self::ID_LF003) ? "regulacao.lf003_itens_segurados_amostras" :  "regulacao.lf034_itens_segurados_amostras";
        $dtCriacao = date('Y-m-d H:i:s');

        if ($item['ds_tipo_amostragem'] == 'MANUAL') {

            if (empty($item['manual'])) {
                return;
            }

            foreach ($item['manual']['amostras'] as $indexAmostra => $amostra) {
            
                $nrAmostra = $indexAmostra + 1;

                $valuesItensSeguradosAmostras = [
                    'id_usuario_criacao' => $this->usuario['id'],
                    'dt_criacao' => $dtCriacao,
                    'id_laudo_final_item_segurado' => $idLaudoFinalItemSegurado,
                    'nr_amostra' => $nrAmostra,
                    'vl_espacamento_entre_linhas' => $amostra['vl_espacamento'],
                    'vl_metros_linhas_colhidos' => $amostra['vl_metro_linear_colhido'],
                    'vl_producao_colhida_amostra' => $amostra['vl_producao_colhida'],
                    'vl_perda_colheita' => $amostra['vl_percentual_perda_colheita'],
                    'vl_umidade_graos' => $amostra['vl_percentual_umidade_graos'],
                    'vl_desconto_umidade_graos' => $amostra['vl_percentual_desconto_umidade'],
                    'vl_producao_liquida' => $amostra['vl_producao_liquida'],
                    'vl_area_colhida' => $amostra['vl_m2_colhido'],
                    'ds_coordenadas' => json_encode($amostra['ds_coordenada']),
                ];

                $rs = $this->db->insert($tabelaAmostras, $valuesItensSeguradosAmostras);

                if (empty($rs)) {
                    throw new Exception("Erro ao incluir a amostra {$nrAmostra} do item {$item['id_item_segurado']} da vistoria {$this->vistoria['id_vistoria']}.");
                }

            }
        } else {
            
            if (empty($item['mecanizado'])) {
                return;
            }
            
            $valuesItensSeguradosAmostras = [
                'id_usuario_criacao' => $this->usuario['id'],
                'dt_criacao' => $dtCriacao,
                'id_laudo_final_item_segurado' => $idLaudoFinalItemSegurado,
                'nr_amostra' => 1,
                'vl_producao_colhida_amostra' => $item['mecanizado']["vl_producao_colhida"],
                'vl_perda_colheita' => $item['mecanizado']["vl_percentual_perda_colheita"] ,
                'vl_umidade_graos' => $item['mecanizado']["vl_umidade_graos"],
                'vl_desconto_umidade_graos' => $item['mecanizado']["vl_percentual_desconto_umidade"],
                'vl_area_colhida' => ($item['mecanizado']['vl_distancia_percorrida'] * $item['mecanizado']['vl_largura_plataforma']) / 10000,
                'vl_largura_plataforma' => $item['mecanizado']["vl_largura_plataforma"],
                'vl_distancia_percorrida' => $item['mecanizado']["vl_distancia_percorrida"],
                'vl_impurezas' => $item['mecanizado']["vl_percentual_impurezas"],
                'ds_coordenadas' => json_encode($item['mecanizado']["ds_coordenadas"]),
                'vl_m2_perda_colheita' => $item['mecanizado']["vl_m2_perda_colheita"], // Criado nesta tarefa (2310111037)
                'vl_producao_perdida_colheita' => $item['mecanizado']["vl_producao_perdida_colheita"] // Criado nesta tarefa (2310111037)
            ];

            $rs = $this->db->insert($tabelaAmostras, $valuesItensSeguradosAmostras);

            if (empty($rs)) {
                throw new Exception("Erro ao incluir a amostra 1 do item {$item['id_item_segurado']} da vistoria {$this->vistoria['id_vistoria']}.");
            }
        }
    }

    /**
     * Salva os dados específicos por laudos de um item dependendo do tipo de cobertura.
     * Tabelas: regulacao.lf003_itens_segurados ou regulacao.lf034_itens_segurados
     *
     * @param array $item
     * @param int $idLaudoFinalItemSegurado
     */
    private function salvarItensSeguradosLaudos(array $item, int $idLaudoFinalItemSegurado): void
    {
        $tabela = ($this->idTipoLaudoFinal == self::ID_LF003) ? "regulacao.lf003_itens_segurados" :  "regulacao.lf034_itens_segurados";
        $dtCriacao = date('Y-m-d H:i:s');

        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => $dtCriacao,
            'id_laudo_final_item_segurado' => $idLaudoFinalItemSegurado,
            'fl_amostragem_maquina' => $item['ds_tipo_amostragem'] == 'MECANIZADA' ? 1 : 0,
            'vl_graos_podres' => $item['vl_percentual_graos_poderes'],
            'vl_graos_brotados' => $item['vl_percentual_graos_brotados'],
            'vl_pragas_doencas_outros' => $item['vl_percentual_pragas_doencas_outros'],
            'vl_percentual_graos_avariados' => $item['vl_percentual_graos_avariados'], // Criado nesta tarefa (2310111037)
        ];

        $rs = $this->db->insert($tabela, $values);

        if (empty($rs)) {
            throw new Exception("Erro ao incluir os dados do item {$item['id_item_segurado']} da {$this->idTipoLaudoFinal} da vistoria {$this->vistoria['id_vistoria']}.");
        }        
    }

    /**
     * Salva as assinaturas dos agrônomos.
     * Tabela: sinistro.laudos_finais_agronomos_agro
     */
    private function salvarLaudosFinaisAgronomosAgro(): void
    {
        if (empty($this->vistoria['assinaturas_agronomos'])) {
            return;
        }

        $dtCriacao = date('Y-m-d H:i:s');
        foreach ($this->vistoria['assinaturas_agronomos'] as $agronomo) {

            $values = [
                'id_usuario_criacao' => $this->usuario['id'],
                'dt_criacao' => $dtCriacao,
                'id_laudo_final' => $this->idLaudoFinal,
                'id_funcionario' => $this->getIdFuncionarioByUsuario($agronomo['id_usuario']),
                'svg_assinatura' => $agronomo['ds_assinatura']
            ];

            $rs = $this->db->insert("sinistro.laudos_finais_agronomos_agro", $values);

            if (empty($rs)) {
                throw new Exception("Erro ao incluir a assinatura do agrônomo {$agronomo['id_usuario']} da vistoria {$this->vistoria['id_vistoria']}.");
            }
        }
    }

    /**
     * Salva as assinaturas do responsável, do perito principal e do perito secundário
     * Tabela: sinistro.laudos_finais_assinaturas
     */
    private function salvarLaudosFinaisAssinaturas(): void
    {
        $dtCriacao = date('Y-m-d H:i:s');
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => $dtCriacao,
            'id_laudo_final' => $this->idLaudoFinal,
            'svg_assinatura_responsavel' => (!empty($this->vistoria['acompanhante'])) ? $this->vistoria['acompanhante']['ds_assinatura_acompanhante'] : '',
            'svg_assinatura_perito_principal' => $this->getAssinaturaPerito(),
            'svg_assinatura_perito_secundario' => $this->getAssinaturaPerito(false),
        ];

        $rs = $this->db->insert("sinistro.laudos_finais_assinaturas", $values);

        if (empty($rs)) {
            throw new Exception("Erro ao incluir as assinaturas do responsável e dos peritos da vistoria {$this->vistoria['id_vistoria']}.");
        }

        $this->salvarLaudosFinaisFuncionarios();
    }

    /**
     * Salva fotos das vistorias dispensadas
     */
    private function salvarFotosVistoria(): void
    {
        foreach ($this->vistoria['fotos_vistoria'] as $arquivo) {
            $this->salvarLaudosFinaisArquivos(
                $arquivo['ds_nome_arquivo'],
                $arquivo['ds_tipo_arquivo'],
                json_encode($arquivo['ds_coordenada']),
                $arquivo['md5']
            );
        }
    }

    /**
     * Salva os dados do laudo final.
     * Tabela: sinistro.laudos_finais
     */
    private function salvarLaudoFinal(): void
    {
        $dtCriacao = date('Y-m-d H:i:s');
        $deviceId = explode('|', $this->vistoria['deviceId']);
        $idMotivoDispensa = $this->vistoria['id_motivo_dispensa_vistoria'];

        if (!empty($this->vistoria['acompanhante']) && $this->vistoria['acompanhante']['fl_acompanhante_compareceu'] == false) {
            $idMotivoDispensa = $this->getIdMotivoQuadraNaoVistoriada('Acompanhante não estava presente', 'DISPENSA_VISTORIA');
        }

        if ($this->possuiItemConcluidoAutomaticamente) {
            $idMotivoDispensa = self::MOTIVO_VISTORIA_NAO_CONCLUIDA;
        }

        $dados = [
            'id_usuario_alteracao' => $this->usuario['id'],
            'dt_alteracao' => $dtCriacao,
            'ds_observacao' => $this->montarObservacaoLaudoFinal($this->vistoria['ds_observacao_vistoria']),
            'fl_assinatura_regulador' => !empty($this->idFuncionarioPeritoPrincipal) ? 1 : 0,
            'fl_assinatura_segurado' => !empty($this->vistoria['ds_assinatura_acompanhante']) ? 1 : 0,
            'fl_evento_nao_constatado' => !empty($this->vistoria['id_motivo_dispensa_vistoria']) || $idMotivoDispensa == self::MOTIVO_VISTORIA_NAO_CONCLUIDA ? 1 : 0,
            'id_status' => $this->getIdStatus('TRANSMITIDA_SEM_FOTOS', 'LF'),
            'ds_coordenadas' => $this->getCoordenadaVistoria(),
            'fl_transmitido_seguradora' => 'true',
            'fl_dt_evento_em_cobertura' => $this->isDataEventoEmCobertura() ? 1 : 0,
            'dt_vistoria' => $this->getDataInicioVistoria(),
            'vl_area_vistoriada' => $this->getAreaVistoriada(),
            'fl_area_declarada_representa_area_cultivada' => $this->vistoria['fl_representa_toda_area'] ? 1 : 0,
            //'ds_nome_laudo_fisico' => '', // Possivelmente o laudo de evento não constatado
            'fl_transmitido' => 'true', // Criado na tabela 2310201644 (QUANDO RECEBEMOS A INFORMACAO DO LAUDO DE VOLTA DO APP)
            'fl_transmitido_tablet' => 'true', // Criado na tabela 2310201644 (TRUE SE FOI FEITO NO APP, FALSE SE FOI FEITO MANUAL)
            'dt_transmitido_tablet' => $dtCriacao, // Criado na tabela 2310201644
            //'dt_laudo' => '', // Criado na tabela 2310201644 -  Não precisa preencher agora, somente quando o laudo for gerado
            'id_tablet' => $deviceId[0], // Criado na tabela 2310201644
            //'fl_was_cleaned_in_tablet' => '', // Criado na tabela 2310201644 Não precisa preencher agora, precisa criar uma rota para setar esse valor
            'id_motivo_vistoria_nao_vistoriada' => $idMotivoDispensa, //Criado na tarefa 2310271211
            'version_db' => $this->vistoria['versionDB'], //Criado nesta tarefa (2310111037)
            'version_name' => $this->vistoria['versionName'], //Criado nesta tarefa (2310111037)
        ];

        if (!empty($this->vistoria['acompanhante'])) {
            $tipoAcompanhante = strtolower($this->vistoria['acompanhante']['ds_tipo_acompanhante']);
            $dados['ds_responsavel'] = $this->vistoria['acompanhante']['ds_nome_acompanhante']; //Criado na tarefa 2310271211
            $dados['cpf_responsavel'] = $this->vistoria['acompanhante']['ds_cpf_acompanhante']; //Criado na tarefa 2310271211
            $dados['ds_tipo_responsavel'] = $tipoAcompanhante; //Criado na tarefa 2310271211
            $dados['fl_acompanhante_possui_autorizacao'] = $this->vistoria['acompanhante']['fl_acompanhante_possui_autorizacao'] ? 1 : 0; //Criado nesta tarefa (2310111037)

            if ($tipoAcompanhante == 'outro_pendente_autorizacao') {
                $this->adicionarFilaEnvioDocusign();
            }
        }

        if (!empty($this->vistoria['segurado'])) {
            $dados['ds_tipo_envio_doc_acompanhante'] = $this->vistoria['segurado']['tp_envio_doc_acompanhante']; //Criado nesta tarefa (2310111037)
            $dados['ds_tipo_envio_laudo'] = $this->vistoria['segurado']['tp_envio_laudo']; //Criado nesta tarefa (2310111037)
            $dados['ds_email_segurado'] = $this->vistoria['segurado']['ds_email_segurado']; //Criado nesta tarefa (2310111037)
            $dados['ds_telefone_segurado'] = $this->vistoria['segurado']['ds_telefone_segurado']; //Criado nesta tarefa (2310111037)
        }

        $this->db->update('sinistro.laudos_finais', $dados, "id = '{$this->idLaudoFinal}'");
    }

    /**
     * Monta a observação do laudo final, verificando se há itens fora de zoneamento
     * para adicionar na observação
     *
     * @param string|null $dsObservacao
     * @return string
     */
    private function montarObservacaoLaudoFinal(?string $dsObservacao): string
    {
        $observacao = !empty($dsObservacao) ? $dsObservacao : '';
        $itensForaZoneamento = $this->getItensForaZoneamento();

        if (!empty($itensForaZoneamento)) {
            if (!empty($observacao)) {
                $observacao .= " | ";
            }

            $nomesItens = implode($itensForaZoneamento['ds_item_segurado'], $itensForaZoneamento);
            $observacao .= "Área negada por estar fora do zoneamento Quadra (s): {$nomesItens}.";
        }

        return $observacao;
    }

    /**
     * Busca os itens segurados que estão fora de zoneamento
     *
     * @return array
     */
    private function getItensForaZoneamento(): array
    {
        $query = $this->db->select();
        $query->from(['sis' => 'seguro.itens_segurados'], false);
        $query->join(['lfis' => 'sinistro.laudos_finais_itens_segurados'], 'lfis.id_item_segurados = sis.id', ['nr_item_segurado', 'ds_item_segurado']);
        $query->where('lfis.id_laudo_final = ?', $this->idLaudoFinal);
        $query->where('lfis.fl_zoneamento_valido is false');
        $query->order('sis.nr_item_segurado');

        $rs = $this->db->fetchAll($query);
        return !empty($rs) ? $rs : [];
    }

    /**
     * Adiciona o envio do documento de autorização do acompanhante na fila da docusign
     * Tabela: sinistro.laudos_finais_docusign
     */
    private function adicionarFilaEnvioDocusign(): void
    {
        $dtCriacao = date('Y-m-d H:i:s');
        
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => $dtCriacao,
            'id_laudo_final' => $this->idLaudoFinal,
            'ds_status' => 'waiting'
        ];
        $rs = $this->db->insert('sinistro.laudos_finais_docusign', $values);
        
        if (empty($rs)) {
            throw new Exception("Erro ao adicionar o envio do documento de autorização do acompanhante.");
        }
    }

    /**
     * Valida se o motivo de dispensa da vistoria foi informado e se o mesmo é referente ao motivo
     * 'A data de ocorrência do evento está fora do período de cobertura'.
     *
     * @return bool
     */
    private function isDataEventoEmCobertura(): bool
    {
        if (empty($this->vistoria['id_motivo_dispensa_vistoria'])) {
            return false;
        }

        $dsMotivo = 'A data de ocorrência do evento está fora do período de cobertura';
        $idMotivoDataOcorrencia = $this->getIdMotivoQuadraNaoVistoriada($dsMotivo, 'DISPENSA_VISTORIA');
        return $this->vistoria['id_motivo_dispensa_vistoria'] == $idMotivoDataOcorrencia;
    }

    /**
     * Percorre os dados para salvar os documentos do acompanhante
     */
    private function salvarDocumentosAcompanhante(): void
    {
        if (empty($this->vistoria['acompanhante'])) {
            return;
        }

        foreach ($this->vistoria['acompanhante']['documentos']['arquivos'] as $arquivo) {

            $this->salvarLaudosFinaisArquivos(
                $arquivo['ds_nome_arquivo'],
                $arquivo['ds_tipo_arquivo'],
                json_encode($arquivo['ds_coordenada']),
                $arquivo['md5']
            );
        }
        $video = $this->vistoria['acompanhante']['documentos']['video'];
        
        if (!empty($video)) {
            $this->salvarLaudosFinaisArquivos(
                $video['ds_nome_arquivo'],
                'VIDEO_ACOMPANHANTE',
                json_encode($video['ds_coordenada'])
            );
        }
    }

    /**
     * Salva os documentos informados do acompanhante
     * Tabela: sinistro.laudos_finais_arquivos
     *
     * @param string $dsCaminhoArquivo
     * @param string $dsTipoArquivo
     * @param string $dsCoordenada
     * @param string $md5
     */
    private function salvarLaudosFinaisArquivos(string $dsCaminhoArquivo, string $dsTipoArquivo, ?string $dsCoordenada = null, ?string $md5 = null): void
    {
        $dtCriacao = date('Y-m-d H:i:s');
        $dsExplodeArquivo = explode("/",  $dsCaminhoArquivo);
        $dsNomeArquivo = end($dsExplodeArquivo);
        
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => $dtCriacao,
            'id_laudo_final' => $this->idLaudoFinal,
            'ds_nome_arquivo' => $dsNomeArquivo,
            'id_tipo_arquivo_laudo_final' => self::TIPOS_DOCUMENTOS[$dsTipoArquivo], //Criado nesta tarefa (2310111037)
            'ds_coordenada' => $dsCoordenada, //Criado nesta tarefa (2310111037)
            'md5' => $md5, //Criado nesta tarefa (2310111037)
        ];
        $rs = $this->db->insert('sinistro.laudos_finais_arquivos', $values);
        
        if (empty($rs)) {
            throw new Exception("Erro ao salvar o arquivo {$dsTipoArquivo} do acompanhante.");
        }
    }

    /**
     * Busca o ID do motivo da quadra não vistoriada.
     * 
     * @param string $dsMotivo
     * @param string $dsChaveTipoMotivo
     * @return int
     */
    private function getIdMotivoQuadraNaoVistoriada(string $dsMotivo, string $dsChaveTipoMotivo): int
    {
        $query = $this->db->select();
        $query->from(['m' => 'sinistro.motivos_quadra_nao_vistoriada'], 'id');
        $query->join(['mt' => 'sinistro.motivos_quadra_nao_vistoriada_tipos'], 'mt.id_motivo_quadra_nao_vistoriada = m.id', null);
        $query->join(['tm' => 'sinistro.tipos_motivos_quadra_nao_vistoriada'], 'tm.id = mt.id_tipo_motivo_quadra_nao_vistoriada', null);
        $query->where('m.ds_motivo = ?', $dsMotivo);
        $query->where('tm.ds_chave = ?', $dsChaveTipoMotivo);

        $rs = $this->db->fetchRow($query);

        if (empty($rs)) {
            throw new Exception("Não foi encontrado o motivo de quadra não vistoriada.");
        }

        return $rs['id'];
    }

    /**
     * Busca o ID do status de acordo com as chaves informadas.
     * 
     * @param string $dsChaveStatus
     * @param string $dsChaveTipoStatus
     * @return int
     */
    private function getIdStatus(string $dsChaveStatus, string $dsChaveTipoStatus): int
    {
        $query = $this->db->select();
        $query->from('sistema.status', 'status.id');
        $query->join('sistema.tipo_status', 'status.id_tipo_status = tipo_status.id', null);
        $query->where('status.ds_chave = ?', $dsChaveStatus);
        $query->where('tipo_status.ds_chave = ?', $dsChaveTipoStatus);
        
        $rs = $this->db->fetchRow($query);

        if (empty($rs)) {
            throw new Exception("Não foi encontrado o status {$dsChaveStatus}.");
        }

        return $rs['id'];
    }

     /**
      * Retorna a coordenada da foto do documento de frente do acompanhante (FOTO_DOC_FRENTE). Caso não haja,
      * busca a coordenada do vídeo.
      *
      * @return string
      */
    public function getCoordenadaVistoria(): string
    {
        if (!is_null($this->dsCoordenadaVistoria)) {
            return $this->dsCoordenadaVistoria;
        }

        $this->dsCoordenadaVistoria = '';

        if (empty($this->vistoria['acompanhante'])) {
            return $this->dsCoordenadaVistoria;
        }

        if (!empty($this->vistoria['acompanhante']['documentos']['video'])) {
            $video = $this->vistoria['acompanhante']['documentos']['video'];

            $latitude = $this->formatarDecimaisCoordenadas($video['ds_coordenada']['latitude']);
            $longitude = $this->formatarDecimaisCoordenadas($video['ds_coordenada']['longitude']);

            $this->dsCoordenadaVistoria = $latitude ." , ". $longitude;
            
            return $this->dsCoordenadaVistoria;
        }

        foreach ($this->vistoria['acompanhante']['documentos']['arquivos'] as $arquivo) {
            if ($arquivo['ds_tipo_arquivo'] == 'FOTO_DOC_FRENTE' && !empty($arquivo['ds_coordenada'])) {

                $latitude = $this->formatarDecimaisCoordenadas($arquivo['ds_coordenada']['latitude']);
                $longitude = $this->formatarDecimaisCoordenadas($arquivo['ds_coordenada']['longitude']);

                $this->dsCoordenadaVistoria = $latitude ." , ". $longitude;
                return $this->dsCoordenadaVistoria;
            }
        }

        return $this->dsCoordenadaVistoria;
    }

    /**
     * Retorna o total de área vistoriada dos itens.
     *
     * @return float
     */
    private function getAreaVistoriada(): float
    {
        if (!is_null($this->vlAreaVistoriadaTotal)) {
            return $this->vlAreaVistoriadaTotal;
        }

        $this->vlAreaVistoriadaTotal = 0.00;

        if (!empty($this->vistoria['itens'])) {
            foreach ($this->vistoria['itens'] as $item) {
                $this->vlAreaVistoriadaTotal += $item['vl_area_vistoriada'];
            }
        }

        return $this->vlAreaVistoriadaTotal;
    }

    /**
     * Retorna a assinatura do perito.
     *
     * @param bool $flPrincipal
     * @return string|null
     */
    private function getAssinaturaPerito(bool $flPrincipal = true): ?string
    {
        $assinatura = '';

        if (!empty($this->vistoria['assinaturas_peritos'])) {
            foreach ($this->vistoria['assinaturas_peritos'] as $perito) {
                if ($perito['fl_principal'] == $flPrincipal) {

                    if ($perito['fl_principal']) {
                        //Embora seja id_usuario que retorna do JSON, na verdade é o id_funcionario
                        $this->idFuncionarioPeritoPrincipal = (int) $perito['id_usuario'];
                    }
                    
                    $assinatura = $perito['ds_assinatura'];
                    break;
                }
            }
        }

        return $assinatura;
    }

    /**
     * Salva o perito principal que assinou o laudo. Caso não haja, busca o perito principal que está no agendamento.
     * Tabela: sinistro.laudos_finais_funcionarios
     */
    private function salvarLaudosFinaisFuncionarios()
    {
        $dtCriacao = date('Y-m-d H:i:s');
        $idFuncionario = $this->getIdFuncionarioPeritoPrincipal();

        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => $dtCriacao,
            'id_laudo_final' => $this->idLaudoFinal,
            'id_funcionario' => $idFuncionario
        ];

        $rs = $this->db->insert("sinistro.laudos_finais_funcionarios", $values);

        if (empty($rs)) {
            throw new Exception("Erro ao incluir os vistoriadores do laudo final da vistoria {$this->vistoria['id_vistoria']}.");
        }
    }

    /**
     * Retorna o ID do perito principal.
     * Caso haja assinatura, retorna o id vinculado a assinatura, senão busca o id do agendamento.
     *
     * @return int
     */
    private function getIdFuncionarioPeritoPrincipal(): int
    {
        if (!empty($this->idFuncionarioPeritoPrincipal)) {
            return $this->idFuncionarioPeritoPrincipal;
        }

        $query = $this->db->select();
        $query->from(['saf' => 'sistema.agendamentos_funcionarios'], 'saf.id_funcionario');
        $query->join(['sav' => 'sistema.vistorias_agendamentos'], 'sav.id_agendamento = saf.id_agendamento', false);
        $query->where('saf.fl_principal is true');
        $query->where('sav.id_vistoria = ?', $this->vistoria['id_vistoria']);
        
        $rs = $this->db->fetchRow($query);

        if (empty($rs)) {
            throw new Exception("Não foi possível encontrar o perito principal do agendamento da vistoria {$this->vistoria['id_vistoria']}.");
        }

        return $rs['id_funcionario'];
    }

    /**
     * Busca o ID do funcionário através do ID do usuário.
     *
     * @param int $idUsuario
     * @return int
     */
    private function getIdFuncionarioByUsuario(int $idUsuario): int
    {
        $query = $this->db->select();
        $query->from('sistema.funcionarios', 'id');
        $query->where('id_usuario = ?', $idUsuario);
        
        $rs = $this->db->fetchRow($query);

        if (empty($rs)) {
            throw new Exception("Não foi encontrado o funcionário com o ID usuário {$idUsuario}.");
        }

        return $rs['id'];
    }

    /**
     * Salva os logs dos campos alterados na vistoria
     *
     * @param int $idVistoria
     * @param int $idProposta
     * @param array $itens
     * @param array $usuario
     * @param Zend_Db_Adapter_Pdo_Pgsql $db
     */
    public function salvarLog(int $idVistoria, int $idProposta, array $itens, array $usuario, Zend_Db_Adapter_Pdo_Pgsql $db): void
    {
        $this->db = $db;
        foreach ($itens as $item) {
            $values = [
                'id_usuario_criacao' => $usuario['id'],
                'dt_criacao' => $item['dt_alteracao'],
                'id_vistoria' => $idVistoria,
                'id_proposta' => $idProposta,
                'id_laudo_final' => $this->getIdLaudoFinalByVistoria($idVistoria),
                'id_item_segurado' => $item['id_item_segurado'],
                'ds_alteracoes' => json_encode($item['campos'])
            ];

            $rs = $this->db->insert('log.log_alteracoes_vistorias_finais', $values);
            
            if (empty($rs)) {
                throw new Exception("Erro ao incluir o log do item {$item['id_item_segurado']} da vistoria {$idVistoria}.");
            }
        }
    }

    /**
     * Marca as vistorias informadas como limpas do APP
     * 
     * @param int $idVistoria
     * @param array $usuario
     * @param Zend_Db_Adapter_Pdo_Pgsql $db
     */
    public function limparVistoria(int $idVistoria, array $usuario, Zend_Db_Adapter_Pdo_Pgsql $db): void
    {
        $this->db = $db;
        $idLaudoFinal = $this->getIdLaudoFinalByVistoria($idVistoria);
        $dados = [
            "id_usuario_alteracao" => $usuario['id'],
            "dt_alteracao" => "now()",
            "fl_was_cleaned_in_tablet" => "true"
        ];

        $rs = $this->db->update('sinistro.laudos_finais', $dados, "id = '{$idLaudoFinal}'");

        if (empty($rs)) {
            throw new Exception("Erro ao atualizar que a vistoria {$idVistoria} foi limpa do tablet.");
        }
    }

    /**
     * Atualiza o status da vistoria no App
     *
     * @param arrau $vistoria
     * @param array $usuario
     * @param Zend_Db_Adapter_Pdo_Pgsql $db
     */
    public function atualizarStatusVistoria(array $vistoria, array $usuario, Zend_Db_Adapter_Pdo_Pgsql $db): void
    {
        $this->db = $db;
        $idLaudoFinal = $this->getIdLaudoFinalByVistoria($vistoria['id_vistoria']);
        $idStatusTabletAtual = $this->getIdStatusTabletAtual($idLaudoFinal);
        
        if (!empty($idStatusTabletAtual)) {
            //Para evitar requisições duplicadas
            if ($idStatusTabletAtual == $vistoria['id_status']) {
                throw new Exception("Erro ao atualizar o status. Vistoria já possui esse status definido.");
            }

            //Para evitar voltar o status da vistoria para "Em andamento" após a mesma já ter sido concluída.
            if ($idStatusTabletAtual == 220 && $vistoria['id_status'] == 218) {
                throw new Exception("Erro ao atualizar o status. Vistoria já está com o status de concluída.");
            }
        }

        $dados = [
            "id_usuario_alteracao" => $usuario['id'],
            "dt_alteracao" => "now()",
            "id_status_tablet" => $vistoria['id_status']
        ];

        $rs = $this->db->update('sinistro.laudos_finais', $dados, "id = {$idLaudoFinal}");

        if (empty($rs)) {
            throw new Exception("Erro ao atualizar o status da vistoria {$vistoria['id_vistoria']}.");
        }
    }

    /**
     * Retorna o ID atual do status do laudo final (vistoria)
     *
     * @param int $idLaudoFinal
     * @return int
     */
    private function getIdStatusTabletAtual(int $idLaudoFinal): ?int
    {
        $query = $this->db->select();
        $query->from('sinistro.laudos_finais', 'id_status_tablet');
        $query->where('id = ?', $idLaudoFinal);
        $rsLaudo = $this->db->fetchRow($query);

        if (empty($rsLaudo)) {
            throw new Exception("Erro ao buscar o status atual da vistoria.");
        }

        return $rsLaudo['id_status_tablet'];
    }

    /**
     * Formata a string da latitude ou longitude, adicionando zero a esqueda quando há apenas um número
     *
     * @param string $coordenada
     * @return string
     */
    private function formatarDecimaisCoordenadas(string $coordenada): string
    {
        $coordenadaMap =  array_map(
            function($item) {  return str_pad(trim($item), 2, '0', STR_PAD_LEFT); },
            explode(' ', $coordenada)
        );

        return implode(" ", $coordenadaMap);
    }

    /**
     * Gera uma nova vistoria e um novo agendamento. (Utilizado no Enviar do LF)
     *
     * @param array $vistoria
     * @param array $usuario
     * @param Zend_Db_Adapter_Pdo_Pgsql &$db
     */
    public function novaVistoria(array $vistoria, array $usuario, Zend_Db_Adapter_Pdo_Pgsql &$db): void
    {
        $this->vistoria = $vistoria;
        $this->usuario = $usuario;
        $this->db = $db;
        $this->idLaudoFinal = $this->getIdLaudoFinalByVistoria($this->vistoria['id_vistoria']);
        $this->idPropostaVigente = $this->getPropostaVigenteByLaudoFinal();
        $this->gerarNovaVistoria();
    }

    /**
     * Busca os itens do laudo final com status AGUARDANDO_VISTORIA
     *
     * @return array
     */
    private function getItensAguardandoNovaVistoria(): array
    {
        $query = $this->db->select();
        $query->from(['lf' => 'sinistro.laudos_finais'], ['id_cobertura', 'id_processo', "to_char(coalesce(lf.dt_vistoria, now()) + INTERVAL '20 days', 'YYYY-MM-DD') as dt_nova_vistoria_default"]);
        $query->join(['lfis' => 'sinistro.laudos_finais_itens_segurados'], 'lfis.id_laudo_final = lf.id', ['id_item_segurado', 'dt_nova_vistoria']);
        $query->join(['pe' => 'sinistro.processos_empresas'], 'pe.id_processo = lf.id_processo AND pe.fl_ativo is true', ['id_empresa']);
        $query->join(['st' => 'sistema.status'], 'st.id = lfis.id_status', null);
        $query->join(['ts' => 'sistema.tipo_status'], 'ts.id = st.id_tipo_status', null);
        $query->where('st.ds_chave = ?', 'AGUARDANDO_VISTORIA');
        $query->where('ts.ds_chave = ?', 'ITENS_VISTORIAS_FINAIS');
        $query->where('lfis.id_laudo_final = ?', $this->idLaudoFinal);
        $query->order('lfis.dt_nova_vistoria');

        $rs = $this->db->fetchAll($query);
        return !empty($rs) ? $rs : [];
    }

    /**
     * Verifica se o laudo final está como AGUARDANDO_VISTORIA e retorna os itens da vistoria
     *
     * @return array
     */
    private function getItensLaudoFinalAguardandoVistoria(): array
    {
        $query = $this->db->select();
        $query->from(['lf' => 'sinistro.laudos_finais'], ['id_cobertura', 'id_processo', "to_char(coalesce(lf.dt_vistoria, now())  + INTERVAL '20 days', 'YYYY-MM-DD') as dt_nova_vistoria_default"]);
        $query->join(['pe' => 'sinistro.processos_empresas'], 'pe.id_processo = lf.id_processo AND pe.fl_ativo is true', ['id_empresa']);
        $query->join(['mqnv' => 'sinistro.motivos_quadra_nao_vistoriada'], 'mqnv.id = lf.id_motivo_vistoria_nao_vistoriada', null);
        $query->join(['st' => 'sistema.status'], 'st.id = mqnv.id_status', null);
        $query->join(['ts' => 'sistema.tipo_status'], 'ts.id = st.id_tipo_status', null);
        $query->join(['vlf' => 'sistema.vistorias_laudos_finais'], 'vlf.id_laudo_final = lf.id', null);
        $query->join(['vis' => 'sistema.vistorias_itens_segurados'], 'vis.id_vistoria = vlf.id_vistoria', ["id_item_segurado"]);
        $query->where('st.ds_chave = ?', 'AGUARDANDO_VISTORIA');
        $query->where('ts.ds_chave = ?', 'ITENS_VISTORIAS_FINAIS');
        $query->where('lf.id = ?', $this->idLaudoFinal);

        $rs = $this->db->fetchAll($query);
        return !empty($rs) ? $rs : [];
    }

    /**
     * Gera uma nova vistoria e um novo agendamento com os itens que ficaram pendentes
     */
    private function gerarNovaVistoria(): void
    {
        $rs = $this->getItensAguardandoNovaVistoria();

        if (empty($rs)) {
            $rs = $this->getItensLaudoFinalAguardandoVistoria();
        }

        if (empty($rs)) {
            return;
        }

        $idEmpresa = $rs[0]['id_empresa'];
        $idProcesso = $rs[0]['id_processo'];
        $idCobertura = $rs[0]['id_cobertura'];
        //@todo Verificar no APP os casos onde deveria estar vindo vazio nos itens por não ter sido informado. Atualmente está sempre vindo com a data atual do envio.
        $dtNovaVistoria = $rs[0]['dt_nova_vistoria_default'];

        $idTipoVistoria = 3; //Final
        $idAgendamento = $this->getAgendamentoAberto($idProcesso, $idCobertura, $idTipoVistoria);

        if (!$idAgendamento) {

            $idNovaVistoria = $this->incluirVistoria($idTipoVistoria);
            $idAgendamento = $this->incluirAgendamento($idTipoVistoria, $dtNovaVistoria);
            $idNovoLaudoFinal = $this->incluirNovoLaudoFinal($idProcesso, $idCobertura);
            $this->relacionarAgendamentoFuncionario($idAgendamento);
            $this->relacionarVistoriaCobertura($idNovaVistoria, $idCobertura);
            $this->relacionarVistoriaAgendamento($idNovaVistoria, $idAgendamento);
            $this->relacionarVistoriaProcesso($idNovaVistoria, $idProcesso);
            $this->relacionarVistoriaEmpresa($idNovaVistoria, $idEmpresa);
            $this->relacionarVistoriaAviso($idNovaVistoria, $dtNovaVistoria, $idCobertura);
            $this->relacionarVistoriaLaudoFinal($idNovaVistoria, $idNovoLaudoFinal);
            
            foreach ($rs as $itemSegurado) {
                $this->relacionarVistoriaItemSegurado($idNovaVistoria, $itemSegurado['id_item_segurado']);
            }
        }
    }

    /**
     * Verifica se já existe agendamento final aberto
     *
     * @param int $idProcesso
     * @param int $idCobertura
     * @param int $idTipoVistoria
     */
    private function getAgendamentoAberto(int $idProcesso, int $idCobertura, int $idTipoVistoria)
    {
		$select = $this->db->select();
		$select->from(array('ag'=>'sistema.agendamentos'), 'id as id_agendamento');
		$select->join(array('va'=>'sistema.vistorias_agendamentos'), 'ag.id = va.id_agendamento', null);
		$select->join(array('vp'=>'sinistro.vistorias_processos'), 'vp.id_vistoria = va.id_vistoria', null);
		$select->joinLeft(array('tv'=>'sistema.tipos_vistoria'), 'tv.id = ag.id_tipo_vistoria', null);
		$select->joinLeft(array('st'=>'sistema.status'), 'st.id = ag.id_status', null);
		$select->join(array('vc'=>'sistema.vistorias_coberturas'), 'vc.id_vistoria = va.id_vistoria', null);
        $select->joinLeft(array('vlf'=>'sistema.vistorias_laudos_finais'), 'vlf.id_vistoria = va.id_vistoria', null);
        $select->joinLeft(array('lf'=>'sinistro.laudos_finais'), 'lf.id = vlf.id_laudo_final', null);
        $select->joinLeft(array('st2'=>'sistema.status'), 'st2.id = lf.id_status', null);
		$select->where($this->db->quoteInto('vp.id_processo = ?', $idProcesso));
        $select->where($this->db->quoteInto("(st.ds_chave = 'AGUARDANDO_DATA') OR (st.ds_chave = 'AGENDADO' and ag.dt_agendamento > now() and st2.ds_chave='PENDENTE_LAUDO_FINAL')", null), null);
		$select->where($this->db->quoteInto('vc.id_cobertura = ?', $idCobertura));
		$select->where($this->db->quoteInto('ag.id_tipo_vistoria = ?', $idTipoVistoria));
		$select->where('ag.fl_ativo = true');
		$select->order('ag.dt_criacao desc');
		$select->limit(1);
		return $this->db->fetchOne($select);
	}

    /**
     * Inclui uma nova vistoria final
     * Tabela: sistema.vistorias
     *
     * @param int $idTipoVistoria
     * @return int
     */
    private function incluirVistoria(int $idTipoVistoria): int
    {
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => date('Y-m-d H:i:s'),
            'id_tipo' => $idTipoVistoria
        ];
        $rs = $this->db->insert('sistema.vistorias', $values);
        if (empty($rs)) {
            throw new Exception("Erro ao incluir a nova vistoria.");
        }

        return  $this->db->lastInsertId();
    }

    /**
     * Realiza o vínculo entre a vistoria e a cobertura
     * Tabela: sistema.vistorias_coberturas
     *
     * @param int $idNovaVistoria
     * @param int $idCobertura
     */
    private function relacionarVistoriaCobertura(int $idNovaVistoria, int $idCobertura): void
    {
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => date('Y-m-d H:i:s'),
            'id_vistoria' => $idNovaVistoria,
            'id_cobertura' => $idCobertura
        ];
        $rs = $this->db->insert('sistema.vistorias_coberturas', $values);
        if (empty($rs)) {
            throw new Exception("Erro ao vincular a nova vistoria com a cobertura.");
        }
    }

    /**
     * Inclui um novo agendamento
     * Tabela: sistema.agendamentos
     *
     * @return int
     */
    private function incluirAgendamento(int $idTipoVistoria, string $dtNovaVistoria): int
    {
        $idStatusAgendado = $this->getIdStatus('AGENDADO', 'AGENDAMENTO');
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => date('Y-m-d H:i:s'),
            'id_tipo_vistoria' => $idTipoVistoria,
            'id_status' => $idStatusAgendado,
            'dt_agendamento' => $dtNovaVistoria,
            'ds_turno' => 'M'
        ];
        
        $rs = $this->db->insert('sistema.agendamentos', $values);
        if (empty($rs)) {
            throw new Exception("Erro ao incluir um novo agendamento.");
        }

        return  $this->db->lastInsertId();
    }

    /**
     * Realiza o vínculo entre a vistoria e o agendamento
     * Tabela: sistema.vistorias_agendamentos
     *
     * @param int $idNovaVistoria
     * @param int $idAgendamento
     */
    private function relacionarVistoriaAgendamento(int $idNovaVistoria, int $idAgendamento): void
    {
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => date('Y-m-d H:i:s'),
            'id_vistoria' => $idNovaVistoria,
            'id_agendamento' => $idAgendamento
        ];
        $rs = $this->db->insert('sistema.vistorias_agendamentos', $values);

        if (empty($rs)) {
            throw new Exception("Erro ao vincular a nova vistoria com o agendamento.");
        }
    }

    /**
     * Realiza o vínculo entre a vistoria e o processo
     * Tabela: sinistro.vistorias_processos
     *
     * @param int $idNovaVistoria
     * @param int $idProcesso
     */
    private function relacionarVistoriaProcesso(int $idNovaVistoria, int $idProcesso): void
    {
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => date('Y-m-d H:i:s'),
            'id_vistoria' => $idNovaVistoria,
            'id_processo' => $idProcesso
        ];
        $rs = $this->db->insert('sinistro.vistorias_processos', $values);

        if (empty($rs)) {
            throw new Exception("Erro ao vincular a nova vistoria com o processo.");
        }
    }

    /**
     * Realiza o vínculo entre a vistoria e o processo
     * Tabela: sistema.vistorias_empresas
     *
     * @param int $idNovaVistoria
     * @param int $idEmpresa
     */
    private function relacionarVistoriaEmpresa(int $idNovaVistoria, int $idEmpresa): void
    {
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => date('Y-m-d H:i:s'),
            'id_vistoria' => $idNovaVistoria,
            'id_empresa' => $idEmpresa
        ];
        $rs = $this->db->insert('sistema.vistorias_empresas', $values);

        if (empty($rs)) {
            throw new Exception("Erro ao vincular a nova vistoria com a empresa.");
        }
    }

    /**
     * Realiza o vínculo entre o agendamento e os vistoriadores
     * Tabela: sistema.vistorias_empresas
     *
     * @param int $idAgendamento
     */
    private function relacionarAgendamentoFuncionario(int $idAgendamento): void
    {
        $idVistoriaOriginal = $this->vistoria['id_vistoria'];

        $query = $this->db->select();
        $query->from(['af' => 'sistema.agendamentos_funcionarios'], ['id_funcionario', 'fl_principal']);
        $query->join(['va' => 'sistema.vistorias_agendamentos'], 'va.id_agendamento = af.id_agendamento', false);
        $query->where('va.id_vistoria = ?', $idVistoriaOriginal);
        $rs = $this->db->fetchAll($query);

        if (empty($rs)) {
            return;
        }
        
        foreach ($rs as $funcionario) {
            $values = [
                'id_usuario_criacao' => $this->usuario['id'],
                'dt_criacao' => date('Y-m-d H:i:s'),
                'id_agendamento' => $idAgendamento,
                'id_funcionario' => $funcionario['id_funcionario'],
                'fl_principal' => $funcionario['fl_principal'] ? 1 : 0
            ];
            $rs = $this->db->insert('sistema.agendamentos_funcionarios', $values);
    
            if (empty($rs)) {
                throw new Exception("Erro ao vincular o agendamento ao vistoriador.");
            }
        }
 
    }

    /**
     * Realiza o vínculo entre a vistoria e os avisos
     * Tabela: sinistro.vistorias_avisos
     *
     * @param int $idAgendamento
     */
    private function relacionarVistoriaAviso(int $idNovaVistoria, string $dtNovaVistoria, int $idCobertura): void
    {
        $avisos = $this->getAvisos($idNovaVistoria, $dtNovaVistoria, $idCobertura);

        foreach ($avisos as $aviso) {
            $values = [
                'id_usuario_criacao' => $this->usuario['id'],
                'dt_criacao' => date('Y-m-d H:i:s'),
                'id_vistoria' => $idNovaVistoria,
                'id_aviso' => $aviso['id']
            ];
            $rs = $this->db->insert('sinistro.vistorias_avisos', $values);

            if (empty($rs)) {
                throw new Exception("Erro ao vincular a vistoria ao(s) aviso(s).");
            }
        }
    }

    /**
     * Retornas os avisos que devem ser vinculados a nova vistoria
     *
     * @param int $idNovaVistoria
     * @param string $dtNovaVistoria
     * @param int $idCobertura
     * @return array
     */
    private function getAvisos(int $idNovaVistoria, string $dtNovaVistoria, int $idCobertura): array
    {
        $avisos = $this->getAvisosAntesDataVistoria($idNovaVistoria, $dtNovaVistoria, $idCobertura);

        if (empty($avisos)) {
            $avisos = $this->getAvisosUltimaVistoriaRealizada($idNovaVistoria, $dtNovaVistoria, $idCobertura);

            if (empty($avisos)) {
                throw new Exception("Não existem avisos disponíveis anteriores a esta vistoria.");
            }
        }

        return $avisos;
    }

    /**
     * Retorna os avisos com data de sinistro anterior a data da nova vistoria
     *
     * @param int $idNovaVistoria
     * @param string $dtNovaVistoria
     * @param int $idCobertura
     * @return array
     */
    private function getAvisosAntesDataVistoria(int $idNovaVistoria, string $dtNovaVistoria, int $idCobertura): array
    {
        $subQuery = $this->db->select();
        $subQuery->from(['ae'=>'sinistro.avisos_eventos'], 'id_aviso');
        $subQuery->join(['ce'=>'produto.coberturas_eventos'], 'ce.id_evento = ae.id_evento', null);
        $subQuery->where('ce.id_cobertura = ?', $idCobertura);

        $query = $this->db->select();
        $query->from(['a'=>'sinistro.avisos'], 'id');
        $query->join(['st'=>'sistema.status'], "st.id = a.id_status and st.ds_chave = 'ACEITO'", null);
        $query->where('a.id_proposta = '.$this->idPropostaVigente);
        $query->where('a.dt_sinistro < ?', $dtNovaVistoria);
        $query->where('a.id in (?)', $subQuery);
        $query->where('a.id not in (select id_aviso from sinistro.vistorias_avisos where id_vistoria <> '.$idNovaVistoria.' and id_vistoria in (select id_vistoria from sistema.vistorias_laudos_finais))');

        $rs = $this->db->fetchAll($query);
        return !empty($rs) ? $rs : [];
    }

    /**
     * Retorna os avisos da ultima vistoria realizada
     *
     * @param int $idNovaVistoria
     * @param string $dtNovaVistoria
     * @param int $idCobertura
     * @return array
     */
    private function getAvisosUltimaVistoriaRealizada(int $idNovaVistoria, string $dtNovaVistoria, int $idCobertura): array
    {
        $queryMaxIdVistoria =  $this->db->select();
        $queryMaxIdVistoria->from(['vlf'=>'sistema.vistorias_laudos_finais'], 'max(vlf.id_vistoria)');
        $queryMaxIdVistoria->join(['lf'=>'sinistro.laudos_finais'], 'lf.id = vlf.id_laudo_final', null);
        $queryMaxIdVistoria->join(['sp'=>'sinistro.processos'], 'sp.id = lf.id_processo', null);
        $queryMaxIdVistoria->join(['st'=>'sistema.status'], 'st.id = lf.id_status', null);
        $queryMaxIdVistoria->where('lf.id_cobertura  = ?', $idCobertura);
        $queryMaxIdVistoria->where('sp.id_proposta   = ?', $this->idPropostaVigente);
        $queryMaxIdVistoria->where('vlf.id_vistoria <> ?', $idNovaVistoria);
        $queryMaxIdVistoria->where('st.ds_chave not in (\'PENDENTE_LAUDO_FINAL\', \'CANCELADO\', \'CANCELADO_CULPA_SEGURADO\')');

        $idMaxVistoriaCobertura = $this->db->fetchOne($queryMaxIdVistoria);

        $subQuery = $this->db->select();
        $subQuery->from(['ae'=>'sinistro.avisos_eventos'], 'id_aviso');
        $subQuery->join(['ce'=>'produto.coberturas_eventos'], 'ce.id_evento = ae.id_evento', null);
        $subQuery->where('ce.id_cobertura = ?', $idCobertura);

        $query =  $this->db->select();
        $query->from(['a'=>'sinistro.avisos'], 'id');
        $query->join(['st'=>'sistema.status'], "st.id = a.id_status and st.ds_chave = 'ACEITO'", null);
        $query->where('a.id_proposta = '.$this->idPropostaVigente);
        $query->where('a.dt_sinistro < ?', $dtNovaVistoria);
        $query->where('a.id in (?)', $subQuery);

        if($idMaxVistoriaCobertura){
            $query->where('a.id in (select id_aviso from sinistro.vistorias_avisos where id_vistoria = '.$idMaxVistoriaCobertura.')');
        }

        $rs = $this->db->fetchAll($query);
        return !empty($rs) ? $rs : [];
    }

    /**
     * Cria um novo laudo final
     *
     * @param int $idProcesso
     * @param int $idCobertura
     * @return int
     */
    private function incluirNovoLaudoFinal(int $idProcesso, int $idCobertura): int
    {
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => date('Y-m-d H:i:s'),
            'id_processo' => $idProcesso,
            'id_cobertura' => $idCobertura,
            'id_status' => $this->getIdStatus('PENDENTE_LAUDO_FINAL', 'LF')
        ];
        $rs = $this->db->insert('sinistro.laudos_finais', $values);

        if (empty($rs)) {
            throw new Exception("Erro ao criar o novo laudo final.");
        }

        return $this->db->lastInsertId();
    }

    /**
     * Realiza o vínculo entre a vistoria e o laudo final
     * Tabela: sistema.vistorias_laudos_finais
     *
     * @param int $idNovaVistoria
     * @param int $idNovoLaudoFinal
     */
    private function relacionarVistoriaLaudoFinal(int $idNovaVistoria, int $idNovoLaudoFinal): void
    {
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => date('Y-m-d H:i:s'),
            'id_vistoria' => $idNovaVistoria,
            'id_laudo_final' => $idNovoLaudoFinal
        ];
        $rs = $this->db->insert('sistema.vistorias_laudos_finais', $values);

        if (empty($rs)) {
            throw new Exception("Erro ao vincular a vistoria ao laudo final.");
        }
    }

    /**
     * Realiza o vínculo entre a vistoria e os itens segurados que estão pendentes de vistoria (AGUARDANDO_VISTORIA)
     * Tabela: sistema.vistorias_itens_segurados
     *
     * @param int $idNovaVistoria
     * @param int $idItemSegurado
     */
    private function relacionarVistoriaItemSegurado(int $idNovaVistoria, int $idItemSegurado): void
    {
        $values = [
            'id_usuario_criacao' => $this->usuario['id'],
            'dt_criacao' => date('Y-m-d H:i:s'),
            'id_vistoria' => $idNovaVistoria,
            'id_item_segurado' => $idItemSegurado
        ];
        $rs = $this->db->insert('sistema.vistorias_itens_segurados', $values);

        if (empty($rs)) {
            throw new Exception("Erro ao vincular a vistoria ao item {$idItemSegurado}.");
        }
    }

    /**
     * Valida se todos os arquivos da vistoria foram recebidos e altera o status do laudo final
     *
     * @param string $nomeArquivo
     * @param array $usuario
     * @param Zend_Db_Adapter_Pdo_Pgsql $db
     */
    public function validarRecebimentoTodosArquivos(string $nomeArquivo, array $usuario, Zend_Db_Adapter_Pdo_Pgsql $db): void
    {
        $this->db = $db;
        $nomeSeparado = explode("_", $nomeArquivo);
        
        if (empty($nomeSeparado[2])) {
            throw new Exception("Vistoria não informada no nome do arquivo.");
        }

        if (!filter_var($nomeSeparado[2], FILTER_VALIDATE_INT)) {
            throw new Exception("ID da vistoria inválido.");
        }

        $idVistoria = (int) $nomeSeparado[2];
        $idLaudoFinal = $this->getIdLaudoFinalByVistoria($idVistoria);
        $arquivos = $this->getTodosArquivosLaudoFinal($idLaudoFinal);

        $todosArquivosRecebidos = true;
        $diretorio = __DIR__ . '/../../../../application/upload_files/laudofinal/';
        foreach ($arquivos as $arquivo) {
            if (!file_exists($diretorio . $arquivo['ds_nome_arquivo'])){
                $todosArquivosRecebidos = false;
                break;
            }
        }

        if ($todosArquivosRecebidos) {
            $dados = [
                "id_usuario_alteracao" => $usuario['id'],
                "dt_alteracao" => "now()",
                "id_status" => $this->getIdStatus('ENVIADO', 'LF')
            ];
    
            $rs = $this->db->update('sinistro.laudos_finais', $dados, "id = '{$idLaudoFinal}'");

            
            if (empty($rs)) {
                throw new Exception("Erro ao atualizar o status do laudo final.");
            }
        }
    }

    /**
     * Retorna o nome de todos os arquivos do laudo final e de seus itens
     *
     * @param int $idLaudoFinal
     * @return array
     */
    private function getTodosArquivosLaudoFinal(int $idLaudoFinal): array
    {
        $queryLaudosFinaisArquivos = $this->db->select();
        $queryLaudosFinaisArquivos->from(['lfa' => 'sinistro.laudos_finais_arquivos'], 'ds_nome_arquivo');
        $queryLaudosFinaisArquivos->where('lfa.id_laudo_final = ?', $idLaudoFinal);
        $rsLaudosFinaisArquivos = $this->db->fetchAll($queryLaudosFinaisArquivos);

        $queryLaudosFinaisItensArquivos = $this->db->select();
        $queryLaudosFinaisItensArquivos->from(['lfisa' => 'sinistro.laudos_finais_itens_segurados_arquivos'], 'ds_nome_arquivo');
        $queryLaudosFinaisItensArquivos->join(['lfis' => 'sinistro.laudos_finais_itens_segurados'], 'lfis.id = lfisa.id_item_laudo_final', null);
        $queryLaudosFinaisItensArquivos->where('lfis.id_laudo_final = ?', $idLaudoFinal);
        $rsLaudosFinaisItensArquivos = $this->db->fetchAll($queryLaudosFinaisItensArquivos);

        return array_merge($rsLaudosFinaisArquivos, $rsLaudosFinaisItensArquivos);
    }
}
