"""
GitHub Integration for Custom Algorithm Uploads
================================================

This module handles uploading custom algorithms to a dedicated GitHub repository
as separate branches for admin review before merging into the main algorithm library.

Features:
- Create branch for each custom algorithm submission
- Upload algorithm file to branch
- Create PR with algorithm details
- Notify admin for review
- Track submission status

Author: MHA Flow Development Team
License: MIT
"""

import os
import json
import base64
import requests
from typing import Dict, Optional, Any
from datetime import datetime
from pathlib import Path


class GitHubAlgorithmUploader:
    """Upload custom algorithms to GitHub for review"""
    
    def __init__(self, token: Optional[str] = None, repo: Optional[str] = None):
        """
        Initialize GitHub uploader.
        
        Parameters
        ----------
        token : str, optional
            GitHub personal access token (or set GITHUB_TOKEN env var)
        repo : str, optional
            Repository in format 'owner/repo' (or set GITHUB_REPO env var)
        """
        self.token = token or os.environ.get('GITHUB_TOKEN')
        self.repo = repo or os.environ.get('GITHUB_REPO', 'yourusername/mha-custom-algorithms')
        
        if not self.token:
            raise ValueError("GitHub token required. Set GITHUB_TOKEN environment variable or pass token parameter.")
        
        self.api_base = 'https://api.github.com'
        self.headers = {
            'Authorization': f'token {self.token}',
            'Accept': 'application/vnd.github.v3+json',
            'Content-Type': 'application/json'
        }
    
    def create_branch(self, branch_name: str, from_branch: str = 'main') -> Dict[str, Any]:
        """
        Create a new branch for the custom algorithm.
        
        Parameters
        ----------
        branch_name : str
            Name of the new branch
        from_branch : str
            Base branch to create from (default: 'main')
            
        Returns
        -------
        result : dict
            Status and branch information
        """
        try:
            # Get SHA of the base branch
            ref_url = f'{self.api_base}/repos/{self.repo}/git/ref/heads/{from_branch}'
            ref_response = requests.get(ref_url, headers=self.headers)
            
            if ref_response.status_code != 200:
                return {
                    'success': False,
                    'error': f'Failed to get base branch: {ref_response.json()}'
                }
            
            base_sha = ref_response.json()['object']['sha']
            
            # Create new branch
            create_url = f'{self.api_base}/repos/{self.repo}/git/refs'
            create_data = {
                'ref': f'refs/heads/{branch_name}',
                'sha': base_sha
            }
            
            create_response = requests.post(create_url, headers=self.headers, json=create_data)
            
            if create_response.status_code in [200, 201]:
                return {
                    'success': True,
                    'branch': branch_name,
                    'sha': base_sha
                }
            else:
                return {
                    'success': False,
                    'error': f'Failed to create branch: {create_response.json()}'
                }
                
        except Exception as e:
            return {
                'success': False,
                'error': str(e)
            }
    
    def upload_file(self, 
                   file_path: str, 
                   file_content: str, 
                   branch: str,
                   commit_message: str) -> Dict[str, Any]:
        """
        Upload algorithm file to GitHub branch.
        
        Parameters
        ----------
        file_path : str
            Path in repository where file should be saved
        file_content : str
            Content of the file
        branch : str
            Branch to commit to
        commit_message : str
            Commit message
            
        Returns
        -------
        result : dict
            Status and commit information
        """
        try:
            # Check if file exists
            content_url = f'{self.api_base}/repos/{self.repo}/contents/{file_path}'
            params = {'ref': branch}
            get_response = requests.get(content_url, headers=self.headers, params=params)
            
            # Encode content
            content_bytes = file_content.encode('utf-8')
            encoded_content = base64.b64encode(content_bytes).decode('utf-8')
            
            # Prepare data
            data = {
                'message': commit_message,
                'content': encoded_content,
                'branch': branch
            }
            
            # If file exists, we need the SHA for update
            if get_response.status_code == 200:
                data['sha'] = get_response.json()['sha']
            
            # Upload/update file
            put_response = requests.put(content_url, headers=self.headers, json=data)
            
            if put_response.status_code in [200, 201]:
                return {
                    'success': True,
                    'commit': put_response.json()['commit']['sha'],
                    'file_url': put_response.json()['content']['html_url']
                }
            else:
                return {
                    'success': False,
                    'error': f'Failed to upload file: {put_response.json()}'
                }
                
        except Exception as e:
            return {
                'success': False,
                'error': str(e)
            }
    
    def create_pull_request(self,
                          branch: str,
                          title: str,
                          body: str,
                          base_branch: str = 'main') -> Dict[str, Any]:
        """
        Create a pull request for the custom algorithm.
        
        Parameters
        ----------
        branch : str
            Source branch with the algorithm
        title : str
            PR title
        body : str
            PR description
        base_branch : str
            Target branch (default: 'main')
            
        Returns
        -------
        result : dict
            Status and PR information
        """
        try:
            pr_url = f'{self.api_base}/repos/{self.repo}/pulls'
            pr_data = {
                'title': title,
                'body': body,
                'head': branch,
                'base': base_branch
            }
            
            pr_response = requests.post(pr_url, headers=self.headers, json=pr_data)
            
            if pr_response.status_code == 201:
                pr_info = pr_response.json()
                return {
                    'success': True,
                    'pr_number': pr_info['number'],
                    'pr_url': pr_info['html_url'],
                    'state': pr_info['state']
                }
            else:
                return {
                    'success': False,
                    'error': f'Failed to create PR: {pr_response.json()}'
                }
                
        except Exception as e:
            return {
                'success': False,
                'error': str(e)
            }
    
    def submit_algorithm(self,
                        algorithm_name: str,
                        algorithm_file_path: str,
                        algorithm_content: str,
                        metadata: Dict[str, Any],
                        author: str) -> Dict[str, Any]:
        """
        Complete workflow to submit a custom algorithm.
        
        Parameters
        ----------
        algorithm_name : str
            Name of the algorithm
        algorithm_file_path : str
            Local path to algorithm file
        algorithm_content : str
            Content of the algorithm file
        metadata : dict
            Algorithm metadata (description, category, parameters, etc.)
        author : str
            Username of the submitter
            
        Returns
        -------
        result : dict
            Complete submission result with branch and PR info
        """
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        # Upload directly to custom-algorithms branch
        branch_name = 'custom-algorithms'
        
        # Step 1: Upload algorithm file directly to custom-algorithms branch
        file_name = Path(algorithm_file_path).name
        # Use unique filename to avoid conflicts
        unique_filename = f'{algorithm_name.lower()}_{author}_{timestamp}.py'
        repo_path = f'mha_toolbox/algorithms/custom/{unique_filename}'
        
        commit_message = f'Add custom algorithm: {algorithm_name} by {author}'
        
        upload_result = self.upload_file(
            file_path=repo_path,
            file_content=algorithm_content,
            branch=branch_name,
            commit_message=commit_message
        )
        
        if not upload_result['success']:
            return upload_result
        
        # Step 2: Upload metadata JSON
        metadata_content = json.dumps(metadata, indent=2)
        metadata_path = f'mha_toolbox/algorithms/custom/{algorithm_name.lower()}_{author}_{timestamp}_metadata.json'
        
        metadata_result = self.upload_file(
            file_path=metadata_path,
            file_content=metadata_content,
            branch=branch_name,
            commit_message=f'Add metadata for {algorithm_name}'
        )
        
        # Return result - no PR needed, uploaded directly to custom-algorithms branch
        return {
            'success': True,
            'branch': branch_name,
            'algorithm_file_url': upload_result.get('file_url'),
            'message': f'Algorithm {algorithm_name} uploaded to custom-algorithms branch for review.'
        }
    
    def get_submission_status(self, pr_number: int) -> Dict[str, Any]:
        """
        Check the status of a submitted algorithm.
        
        Parameters
        ----------
        pr_number : int
            Pull request number
            
        Returns
        -------
        status : dict
            Current status of the submission
        """
        try:
            pr_url = f'{self.api_base}/repos/{self.repo}/pulls/{pr_number}'
            pr_response = requests.get(pr_url, headers=self.headers)
            
            if pr_response.status_code == 200:
                pr_data = pr_response.json()
                return {
                    'success': True,
                    'state': pr_data['state'],
                    'merged': pr_data['merged'],
                    'mergeable': pr_data.get('mergeable'),
                    'url': pr_data['html_url'],
                    'created_at': pr_data['created_at'],
                    'updated_at': pr_data['updated_at']
                }
            else:
                return {
                    'success': False,
                    'error': f'Failed to get PR status: {pr_response.json()}'
                }
                
        except Exception as e:
            return {
                'success': False,
                'error': str(e)
            }
