#include "IntegrityCheck.h"

#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <pwd.h>

#include <string>


#include "Logger.h"
#include "ProcessConfig.h"


#define SQLITE_DB_LOCK_TIMEOUT       1000                       // SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. (1 sec)
#define SQLITE_DB_LOCK_MAX_RETRY       60                       // SQLITE_BUSY 발생시 재시도 최대 횟수



// 생성자
// - pLocalDb : sqlite memory db 객체에 대한 포인트
CIntegrityCheck::CIntegrityCheck( sqlite3 * pLocalDb )
	: m_pLocalStmt( NULL )
{
	m_pLocalDb = pLocalDb;

}


// 소멸자
CIntegrityCheck::~CIntegrityCheck()
{
	// Local DB  관련 stmt clear 처리.
	// - local db 객체는 memory db 로서 전역 객체이므로.. close 하지 않는다. 	
	if( m_pLocalStmt != NULL )
	{
		sqlite3_finalize( m_pLocalStmt );
		m_pLocalStmt = NULL;
	}
}


// 객체 초기화 함수
// - local db 에서 조회 처리를 수행할 stmt 생성 
bool CIntegrityCheck::Init()
{	
	// Local sqlite db 관련 변수	
	int nResult = 0;
	int nRetryCount = 0;
	
	// 조회 처리를 수행할 stmt 객체 생성.
	// strQuery = "CREATE TABLE meta( resource_id integer, filename_hash text, get_content_length integer, deleted_yn text )";
	std::string strQuery;
	strQuery = "SELECT resource_id, filename_hash, get_content_length FROM meta where deleted_yn = 'N';";

	nResult = sqlite3_prepare( m_pLocalDb, strQuery.c_str(), strQuery.size(), &m_pLocalStmt, NULL );

	// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
	while( nResult == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
	{
		sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT );
		++nRetryCount;

		_LOG( LWAR, "Local db prepare sql SQLITE_BUSY. Retry[%d]", nRetryCount );

		nResult = sqlite3_prepare( m_pLocalDb, strQuery.c_str(), strQuery.size(), &m_pLocalStmt, NULL );
	}

	// 최종 실패시...
	if( nResult != SQLITE_OK )
	{
		LOG( LERR, "sqlite3 prepare sql stmt create failed. [%d][%s]", nResult, sqlite3_errmsg( m_pLocalDb ) );
		
		return false;
	}
		
	return true;
}

// integrity check 작업 수행.
bool CIntegrityCheck::Start()
{
	// 작업 소요 시간 정보 계산을 위한 변수.
	// - 시간 동기화로 인한 문제를 해결하기 위해 clock_gettime() 함수 사용.
	struct timespec timeStart;
	struct timespec timeEnd;
	double	dTimeInterval = 0.0;

	memset( &timeStart, 0x00, sizeof( struct timespec ) );
	memset( &timeEnd, 0x00, sizeof( struct timespec ) );
	
	// 작업 시작 로깅 및 시작 시간 정보 추출.
	_LOG( LINF, "Integrity check start." );
	clock_gettime( CLOCK_MONOTONIC, &timeStart );
	
	// Local sqlite db 관련 변수
	int nResult = 0;
	int nRetryCount = 0;

	
	// zero 파일 생성 처리 관련하여....nobody user 에 대한 uid, gid 정보 추출
	uid_t nobodyUid;
	gid_t nobodyGid;
	struct passwd * pStPwNobody;

	pStPwNobody = getpwnam( "nobody" );	
	if( pStPwNobody != NULL )
	{
		nobodyUid = pStPwNobody->pw_uid;
		nobodyGid = pStPwNobody->pw_gid;
	}
	else
	{
		nobodyUid = 65534;
		nobodyGid = 65534;
	}
	
	// select query 수행.
	nResult = sqlite3_step( m_pLocalStmt );
	nRetryCount = 0;

	while( nResult == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
	{
		sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT );
		++nRetryCount;

		LOG( LWAR, "sqlite3 step SQLITE_BUSY, Retry[%d]", nRetryCount );

		// query 재시도
		nResult = sqlite3_step( m_pLocalStmt );
	}


	// select 조회 결과가 존재하는 경우... SQLITE_ROW (100 ) 을 반환...
	// select 조회 결과가 없는 경우.. SQLITE_DONE (101) 을 반환.

	// select query 수행시 오류가 발생한 경우...
	if( nResult != SQLITE_ROW && nResult != SQLITE_DONE )
	{
		LOG( LERR, "sqlite3 step error.[%d][%s]", nResult, sqlite3_errmsg( m_pLocalDb ) );

		// Local DB 오류 발생시.. DB 에 문제가 있는 것으로 보고.. DB 객체를 초기화한다.
		sqlite3_finalize( m_pLocalStmt );		
		m_pLocalStmt = NULL;

		return false;
	}
	
	unsigned long long ullSelectCount = 0;	
	unsigned long long nResourceId = 0;
	std::string strFileName;
	unsigned long long nFileSize = 0;
	struct stat stFileStat;


	// NEW 2015-11-06 huibong
	// - #25000 에 따른 통계 기능 보강
	unsigned long long ullTotalCountSizeMismatch   = 0;
	unsigned long long ullTotalCountZeroAutoCreate = 0;
	unsigned long long ullTotalCountNotExist       = 0;


	
	// select Query 가 정상적으로 수행된 경우....
	while( nResult == SQLITE_ROW )
	{
		// Query 결과를 변수에 저장 처리.
		nResourceId   = sqlite3_column_int64( m_pLocalStmt, 0 );
		
		strFileName.clear();
		strFileName  = (char *)sqlite3_column_text( m_pLocalStmt, 1 );
		
		nFileSize    = sqlite3_column_int64( m_pLocalStmt, 2 );
		
		// 체크 항목.
		// 파일 존재시
		// - size mismatch 체크
		// 파일 존재 안할 경우...
		// - size 가 0 인 경우.. 0 파일 자동 생성 처리.
		// - 그 외는 not exist 처리.
		

		// 해당 파일 속성 정보 추출.
		if( stat( strFileName.c_str(), &stFileStat ) == 0 )
		{
			// 해당 파일이 존재하는 경우...
			// - Size 비교.
			if( ( unsigned long long )( stFileStat.st_size ) != nFileSize )
			{
				// 파일 size 가 서로 다른 경우..
				// - 해당 파일이 업로드 중인 경우에도 .. meta 정보와 물리 정보가 서로 다를 수 있음.
				// - 따라서 본 프로세스 기동 전 업로드 방지 처리가 되어있는지 체크 후 동작 함.
				++ullTotalCountSizeMismatch;

				_LOG( LERR, "[INTEGRITY] File size mismatch. resource_id[%llu] name[%s] size[%llu] local[%jd]", nResourceId, strFileName.c_str(), nFileSize, stFileStat.st_size );
			}

			// 정규 파일여부 검사
			// - S_ISREG( stFileStat.st_mode ) 을 사용하여 검사 가능하나...
			// - 정합성 검증이므로.. 굳이 할 필요 없으며.. 할 경우.. 처리 속도 저하가 발행하므로.. 안한다.
		}
		else
		{
			// stat 호출 오류인 경우...
			// - multi thread 가 아직 1 process 구조라서.. errno 를 그대로 사용....
			// - 만약 multi thread 구조로 변경할 경우.. errno 그대로 사용하면 안됨.

			if( errno == ENOENT )
			{
				// 해당 파일이 존재하지 않는 경우....
				// - 0 파일인 경우.. 자동 생성 처리...
				// - 그 외에는 로깅 처리.

				if( nFileSize == 0 )
				{
					// 해당 파일이 0 파일인 경우.. 자동으로 생성 처리한다.
					FILE * pFile = fopen( strFileName.c_str(), "w" );
					if( pFile == NULL )
					{
						_LOG( LERR, "[INTEGRITY] Zero File not exist. auto create error. [%llu][%s] [%d][%s]", nResourceId, strFileName.c_str(), errno, strerror( errno ) );

						// ADD 2015-12-08 huibong 
						// - 0 파일 생성 실패시.. not exist count 가 증가되도록 처리. (#25462)
						++ullTotalCountNotExist;
					}
					else
					{
						fclose( pFile );
												
						// 권한을 nobody:nobody 로 변경 처리
						if( chown( strFileName.c_str(), nobodyUid, nobodyGid ) != 0 )
						{
							_LOG( LERR, "[INTEGRITY] Zero File auto create. but chown() to nobody faild. [%llu][%s] [%d][%s]", nResourceId, strFileName.c_str(), errno, strerror( errno ) );
						}
						else
						{
							++ullTotalCountZeroAutoCreate;
							_LOG( LINF, "Integrity zero file auto create. resource_id[%llu] name[%s] size[%llu]", nResourceId, strFileName.c_str(), nFileSize );
						}
					}
				}
				else
				{
					// 0 파일이 아닌 파일이 존재하지 않는 경우..
					++ullTotalCountNotExist;
					_LOG( LERR, "[INTEGRITY] File not exist. resource_id[%llu] name[%s] size[%llu]", nResourceId, strFileName.c_str(), nFileSize );
				}
			}
			else
			{
				// stat 호출 오류...
				_LOG( LERR, "[INTEGRITY] file stat call error. resource_id[%llu] name[%s] size[%llu] [%d][%s]", nResourceId, strFileName.c_str(), nFileSize, errno, strerror( errno ) );
			}
		}

		// 작업 진행 사항 로깅.
		++ullSelectCount;
		if( ullSelectCount % 10000 == 0 && ullSelectCount != 0 )
		{
			_LOG( LINF, "Integrity checking. count[%10llu]", ullSelectCount );
		}


		// Next 결과 처리.
		nResult = sqlite3_step( m_pLocalStmt );
		nRetryCount = 0;

		while( nResult == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
		{
			sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT );
			++nRetryCount;

			LOG( LWAR, "sqlite3 step SQLITE_BUSY, Retry[%d]", nRetryCount );

			// query 재시도
			nResult = sqlite3_step( m_pLocalStmt );
		}
		
		if( nResult != SQLITE_ROW && nResult != SQLITE_DONE )
		{
			LOG( LERR, "sqlite3 step error.[%d][%s]", nResult, sqlite3_errmsg( m_pLocalDb ) );

			// Local DB 오류 발생시.. stmt 객체 초기화.
			sqlite3_finalize( m_pLocalStmt );			
			m_pLocalStmt = NULL;

			return false;
		}
	}
	
	// 작업 완료 후 stmt clear
	sqlite3_finalize( m_pLocalStmt );
	m_pLocalStmt = NULL;
	
	// 작업 완료시 시간 정보 추출 후 소요시간 계산.
	clock_gettime( CLOCK_MONOTONIC, &timeEnd );

	dTimeInterval = timeEnd.tv_sec - timeStart.tv_sec;
	dTimeInterval += ( timeEnd.tv_nsec - timeStart.tv_nsec ) * 1e-9;

	_LOG( LINF, "Integrity check complete. path[%s] total count[%llu], elapsed time: %.1lf sec"
		  , CProcessConfig::GetInstance()->GetJobRoot(), ullSelectCount, dTimeInterval );

	_LOG( LINF, "Integrity check report: not exist[%llu], size mismatch[%llu], zero auto create[%llu]"
		  , ullTotalCountNotExist, ullTotalCountSizeMismatch, ullTotalCountZeroAutoCreate );


	return true;
}
