"""
Git同步服务 - M21

功能: 定期从oc-collab项目仓库拉取数据

依赖:
- GitPython: 仓库克隆/同步

"""

import os
import shutil
import logging
from datetime import datetime
from typing import Optional, Dict, List, Tuple
from dataclasses import dataclass

try:
    from git import Repo, GitCommandError, FetchInfo
except ImportError:
    Repo = None

from backend.config import config
from backend.models.database import Project, get_db, engine
from sqlalchemy.orm import Session


logger = logging.getLogger(__name__)


@dataclass
class SyncResult:
    """同步结果"""
    success: bool
    project_name: str
    files_synced: int = 0
    files_added: int = 0
    files_modified: int = 0
    files_deleted: int = 0
    error_message: Optional[str] = None
    started_at: datetime = None
    completed_at: datetime = None


class GitSyncService:
    """Git同步服务"""
    
    def __init__(self, base_path: str = None):
        """
        初始化
        
        Args:
            base_path: Git仓库根目录，默认配置中的repos_path
        """
        self.base_path = base_path or config.get('repos_path', './data/repos')
        self.logger = logging.getLogger(__name__)
        os.makedirs(self.base_path, exist_ok=True)
    
    def sync_project(self, project_name: str, git_repo: str = None) -> SyncResult:
        """
        同步单个项目
        
        Args:
            project_name: 项目名称
            git_repo: Git仓库路径（可选，从数据库获取）
            
        Returns:
            SyncResult: 同步结果
        """
        started_at = datetime.now()
        result = SyncResult(
            success=False,
            project_name=project_name,
            started_at=started_at
        )
        
        try:
            if not git_repo:
                db = next(get_db())
                try:
                    project = db.query(Project).filter(Project.name == project_name).first()
                    if project:
                        git_repo = project.git_repo
                finally:
                    db.close()
            
            if not git_repo:
                result.error_message = f"项目 '{project_name}' 未配置Git仓库"
                result.completed_at = datetime.now()
                return result
            
            project_path = os.path.join(self.base_path, project_name)
            
            if Repo is None:
                result.error_message = "GitPython未安装"
                result.completed_at = datetime.now()
                return result
            
            if os.path.exists(project_path):
                result = self._pull_changes(project_path, project_name, started_at)
            else:
                result = self._clone_repo(git_repo, project_path, project_name, started_at)
            
        except Exception as e:
            result.error_message = str(e)
            self.logger.error(f"同步项目 '{project_name}' 失败: {e}")
        
        result.completed_at = datetime.now()
        return result
    
    def _clone_repo(self, git_repo: str, project_path: str, project_name: str, started_at: datetime) -> SyncResult:
        """克隆仓库"""
        result = SyncResult(
            success=False,
            project_name=project_name,
            started_at=started_at
        )
        
        try:
            self.logger.info(f"克隆仓库: {git_repo} -> {project_path}")
            repo = Repo.clone_from(git_repo, project_path)
            
            result.success = True
            result.files_synced = self._count_files(project_path)
            self.logger.info(f"克隆成功，共 {result.files_synced} 个文件")
            
        except GitCommandError as e:
            result.error_message = f"Git克隆失败: {str(e)}"
            self.logger.error(result.error_message)
        except Exception as e:
            result.error_message = str(e)
            self.logger.error(f"克隆仓库失败: {e}")
        
        result.completed_at = datetime.now()
        return result
    
    def _pull_changes(self, project_path: str, project_name: str, started_at: datetime) -> SyncResult:
        """拉取最新变更"""
        result = SyncResult(
            success=False,
            project_name=project_name,
            started_at=started_at
        )
        
        try:
            if not os.path.exists(os.path.join(project_path, '.git')):
                result.error_message = f"路径 '{project_path}' 不是有效的Git仓库"
                result.completed_at = datetime.now()
                return result
            
            repo = Repo(project_path)
            
            self.logger.info(f"拉取变更: {project_path}")
            
            fetch_result = repo.remotes.origin.fetch()
            files_added = 0
            files_modified = 0
            files_deleted = 0
            
            for fetch_info in fetch_result:
                if fetch_info.flags & FetchInfo.NEW_HEAD:
                    self.logger.info(f"新分支/提交: {fetch_info.ref}")
            
            if repo.head.is_detached:
                self.logger.warning("当前处于detached HEAD状态")
            
            origin = repo.remotes.origin
            origin.fetch()
            
            local_branch = repo.active_branch
            try:
                tracking_branch = local_branch.tracking_branch()
                if tracking_branch:
                    tracking_branch.commit()
                    self.logger.info(f"已更新到最新提交: {tracking_branch.commit().hexsha[:8]}")
            except Exception as e:
                self.logger.warning(f"无法快进更新: {e}")
            
            for item in repo.index.diff(None):
                if item.new_file:
                    files_added += 1
                elif item.deleted_file:
                    files_deleted += 1
                else:
                    files_modified += 1
            
            for untracked in repo.untracked_files:
                files_added += 1
            
            result.success = True
            result.files_added = files_added
            result.files_modified = files_modified
            result.files_deleted = files_deleted
            result.files_synced = files_added + files_modified + files_deleted
            
            self.logger.info(f"拉取成功，新增: {files_added}, 修改: {files_modified}, 删除: {files_deleted}")
            
        except GitCommandError as e:
            result.error_message = f"Git拉取失败: {str(e)}"
            self.logger.error(result.error_message)
        except Exception as e:
            result.error_message = str(e)
            self.logger.error(f"拉取变更失败: {e}")
        
        result.completed_at = datetime.now()
        return result
    
    def sync_all(self) -> Dict[str, SyncResult]:
        """同步所有项目"""
        results = {}
        db = next(get_db())
        try:
            projects = db.query(Project).filter(Project.status == 'active').all()
            for project in projects:
                if project.git_repo:
                    result = self.sync_project(project.name)
                    results[project.name] = result
                    self._save_sync_log(db, project.id, result)
            db.commit()
        finally:
            db.close()
        return results
    
    def _save_sync_log(self, db: Session, project_id: int, result: SyncResult):
        """保存同步日志到数据库"""
        from backend.models.database import SyncLog
        
        sync_log = SyncLog(
            project_id=project_id,
            sync_type='manual',
            status='success' if result.success else 'failed',
            files_synced=result.files_synced,
            files_added=result.files_added,
            files_modified=result.files_modified,
            files_deleted=result.files_deleted,
            error_message=result.error_message,
            started_at=result.started_at,
            completed_at=result.completed_at
        )
        db.add(sync_log)
    
    def get_last_sync_time(self, project_name: str) -> Optional[datetime]:
        """获取最后同步时间"""
        from backend.models.database import SyncLog, Project
        
        db = next(get_db())
        try:
            project = db.query(Project).filter(Project.name == project_name).first()
            if not project:
                return None
            
            sync_log = db.query(SyncLog).filter(
                SyncLog.project_id == project.id
            ).order_by(SyncLog.started_at.desc()).first()
            
            return sync_log.started_at if sync_log else None
        finally:
            db.close()
    
    def clone_if_not_exists(self, project_name: str, git_repo: str) -> bool:
        """克隆仓库（如果不存在）"""
        project_path = os.path.join(self.base_path, project_name)
        if os.path.exists(project_path):
            self.logger.info(f"仓库已存在: {project_path}")
            return True
        
        result = self._clone_repo(git_repo, project_path, project_name, datetime.now())
        return result.success
    
    def pull_changes_only(self, project_path: str) -> bool:
        """仅拉取变更（不克隆）"""
        if not os.path.exists(os.path.join(project_path, '.git')):
            return False
        
        try:
            repo = Repo(project_path)
            repo.remotes.origin.fetch()
            return True
        except Exception as e:
            self.logger.error(f"拉取变更失败: {e}")
            return False
    
    def get_sync_status(self, project_name: str) -> Dict:
        """获取同步状态"""
        from backend.models.database import Project, SyncLog
        from backend.models.database import ProjectSyncStatus
        
        db = next(get_db())
        try:
            project = db.query(Project).filter(Project.name == project_name).first()
            if not project:
                return {'error': '项目不存在'}
            
            sync_status = db.query(ProjectSyncStatus).filter(
                ProjectSyncStatus.project_id == project.id
            ).first()
            
            last_sync_log = db.query(SyncLog).filter(
                SyncLog.project_id == project.id
            ).order_by(SyncLog.started_at.desc()).first()
            
            return {
                'project_name': project_name,
                'sync_enabled': project.oc_collab_enabled,
                'last_sync_time': sync_status.last_sync_time if sync_status else None,
                'sync_status': sync_status.sync_status if sync_status else 'unknown',
                'files_count': sync_status.files_count if sync_status else 0,
                'last_sync_log': {
                    'status': last_sync_log.status if last_sync_log else None,
                    'files_synced': last_sync_log.files_synced if last_sync_log else 0,
                    'started_at': last_sync_log.started_at if last_sync_log else None
                } if last_sync_log else None
            }
        finally:
            db.close()
    
    def _count_files(self, path: str) -> int:
        """计算目录中的文件数量"""
        count = 0
        for root, dirs, files in os.walk(path):
            count += len(files)
        return count
    
    def get_repo_info(self, project_path: str) -> Optional[Dict]:
        """获取仓库信息"""
        if not os.path.exists(os.path.join(project_path, '.git')):
            return None
        
        try:
            repo = Repo(project_path)
            return {
                'remote_url': repo.remotes.origin.url if repo.remotes else None,
                'branch': repo.active_branch.name,
                'commit_hash': repo.head.commit.hexsha,
                'commit_message': repo.head.commit.message,
                'commit_author': repo.head.commit.author.name,
                'commit_date': repo.head.commit.committed_datetime.isoformat(),
                'is_dirty': repo.is_dirty(),
                'files_count': self._count_files(project_path)
            }
        except Exception as e:
            self.logger.error(f"获取仓库信息失败: {e}")
            return None
