#nullable enable

using log4net.Appender;
using log4net.Core;
using SQLite4Unity3d;
using System.Threading;
using UVC.Extention;

namespace UVC.Log
{
    /// <summary>
    /// SQLite 데이터베이스에 로그를 저장하는 log4net Appender 구현체입니다.
    /// 로그 메시지를 SQLite 데이터베이스 테이블에 저장하고 오래된 로그를 자동으로 정리합니다.
    /// </summary>
    /// <remarks>
    /// 이 클래스는 log4net의 AppenderSkeleton을 상속받아 로그 레코드를 SQLite 데이터베이스에 저장합니다.
    /// 한 달 이상 지난 로그 레코드는 자동으로 삭제되며, 데이터베이스 락 상황에서의 재시도 메커니즘을 포함하고 있습니다.
    /// </remarks>
    /// <example>
    /// log4net 구성 파일(App.config 또는 log4net.config)에서 다음과 같이 설정할 수 있습니다:
    /// <code>
    /// <appender name="SQLiteAppender" type="UVC.Log.SQLiteAppender, AssemblyName">
    ///     <connectionString value="data.db" />
    ///     <param name="Threshold" value="DEBUG" />
    /// </appender>
    /// </code>
    /// 
    /// 코드에서 직접 설정하는 방법:
    /// <code>
    /// var sqliteAppender = new SQLiteAppender();
    /// sqliteAppender.ConnectionString = "data.db";
    /// sqliteAppender.Threshold = log4net.Core.Level.Debug;
    /// 
    /// log4net.Config.BasicConfigurator.Configure(sqliteAppender);
    /// </code>
    /// </example>
    public class SQLiteAppender : AppenderSkeleton
    {
        /// <summary>
        /// SQLite 데이터베이스 파일의 경로를 지정하거나 가져옵니다.
        /// </summary>
        /// <remarks>
        /// 절대 경로 또는 상대 경로로 설정할 수 있으며, 상대 경로는 애플리케이션의 실행 디렉터리를 기준으로 합니다.
        /// </remarks>
        /// <example>
        /// <code>
        /// var sqliteAppender = new SQLiteAppender();
        /// // 절대 경로 사용
        /// sqliteAppender.ConnectionString = "C:\\Logs\\application.db";
        /// // 또는 상대 경로 사용
        /// sqliteAppender.ConnectionString = "Logs\\application.db";
        /// </code>
        /// </example>
        public string ConnectionString { get; set; }

        /// <summary>
        /// 한 달 이상 된 로그 데이터 삭제 여부를 추적합니다.
        /// </summary>
        private bool doDeleted = false;

        /// <summary>
        /// SQLite 데이터베이스 연결을 저장합니다.
        /// </summary>
        private SQLiteConnection? db;

        /// <summary>
        /// 데이터베이스 작업에 대한 동기화 잠금 객체입니다.
        /// </summary>
        private static readonly object _dbLock = new object();

        /// <summary>
        /// 로깅 이벤트를 SQLite 데이터베이스에 기록합니다.
        /// </summary>
        /// <param name="loggingEvent">기록할 로깅 이벤트</param>
        /// <remarks>
        /// 이 메서드는 log4net 프레임워크에 의해 새로운 로그 메시지가 발생할 때마다 호출됩니다.
        /// 빈 메시지는 무시하고, 로그 항목을 데이터베이스에 저장합니다.
        /// 데이터베이스 연결이 없는 경우 새로 생성하고, 필요시 오래된 로그를 삭제합니다.
        /// </remarks>
        protected override void Append(LoggingEvent loggingEvent)
        {
            // 빈 메시지 무시
            if (loggingEvent.RenderedMessage.Trim().IsNullOrEmpty()) return;

            // 로그 항목 생성
            var logEntry = new LogEntry
            {
                Date = loggingEvent.TimeStamp.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
                Thread = loggingEvent.ThreadName,
                Level = loggingEvent.Level.ToString(),
                Logger = loggingEvent.LoggerName,
                Message = loggingEvent.RenderedMessage,
                Exception = loggingEvent.GetExceptionString()
            };

            // 데이터베이스 연결이 없으면 새로 생성
            if (db == null)
            {
                var dbPath = ConnectionString;// System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConnectionString);
                db = new SQLiteConnection(dbPath);
                db.CreateTable<LogEntry>();
            }

            // 한 달 이상 된 로그 데이터 삭제 (최초 1회만 실행)
            if (doDeleted == false)
            {
                //한달 지난 로그 삭제
                db.Execute("DELETE FROM LogEntry WHERE Date < datetime('now', '-1 month', 'localtime');");
                doDeleted = true;
            }

            // 스레드 동기화를 통해 데이터베이스 작업 수행
            lock (_dbLock)
            {
                // 데이터베이스 락 시 재시도 메커니즘
                int retryCount = 100;
                while (retryCount > 0)
                {
                    try
                    {
                        db.Insert(logEntry);
                        break;
                    }
                    catch (SQLiteException ex) when (ex.Message.Contains("database is locked"))
                    {
                        retryCount--;
                        if (retryCount == 0) break; // 최대 재시도 횟수 초과
                        Thread.Sleep(100); // 재시도 전에 100ms 대기
                    }
                }
            }
        }

        /// <summary>
        /// SQLite 데이터베이스에 저장되는 로그 항목 구조를 정의합니다.
        /// </summary>
        /// <remarks>
        /// 이 클래스는 로그 테이블의 스키마를 정의하며, SQLite4Unity3d의 ORM 기능을 사용합니다.
        /// </remarks>
        public class LogEntry
        {
            /// <summary>
            /// 로그 항목의 고유 식별자입니다. 자동 증가되는 기본 키로 사용됩니다.
            /// </summary>
            [PrimaryKey, AutoIncrement]
            public int Id { get; set; }

            /// <summary>
            /// 로그가 생성된 날짜와 시간을 ISO 8601 형식으로 저장합니다.
            /// </summary>
            public string Date { get; set; }

            /// <summary>
            /// 로그를 생성한 스레드의 이름을 저장합니다.
            /// </summary>
            public string Thread { get; set; }

            /// <summary>
            /// 로그 레벨을 저장합니다 (DEBUG, INFO, WARN, ERROR, FATAL 등).
            /// </summary>
            public string Level { get; set; }

            /// <summary>
            /// 로그를 생성한 로거의 이름을 저장합니다.
            /// </summary>
            public string Logger { get; set; }

            /// <summary>
            /// 로그 메시지 내용을 저장합니다.
            /// </summary>
            public string Message { get; set; }

            /// <summary>
            /// 예외 정보를 저장합니다. 예외가 없는 경우 빈 문자열이 될 수 있습니다.
            /// </summary>
            public string Exception { get; set; }
        }
    }
}
