import requests
import base64
import os
import logging
import time
from pathlib import Path
from dotenv import load_dotenv
from urllib.parse import urlencode
from typing import Optional, Tuple

_env_path = Path(__file__).resolve().parent.parent.parent.parent / ".env"
if _env_path.exists():
    load_dotenv(_env_path)
else:
    load_dotenv(Path(__file__).resolve().parent.parent.parent / ".env")

logger = logging.getLogger(__name__)

CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_SECRET = os.getenv("CLIENT_SECRET")
REDIRECT_URI = os.getenv("REDIRECT_URI", "https://nixonstats.info")

# Import token storage for persistent token management
try:
    from token_storage import get_token_storage
    TOKEN_STORAGE_AVAILABLE = True
except ImportError:
    TOKEN_STORAGE_AVAILABLE = False
    logger.warning("Token storage not available, falling back to environment variables only")


def get_authorization_url():
    """
    Generate the authorization URL for initial OAuth flow.
    User needs to visit this URL and authorize the app.
    """
    if not CLIENT_ID:
        raise ValueError("CLIENT_ID is required")
    
    # Show the redirect URI being used
    print(f"Using redirect URI: {REDIRECT_URI}")
    print("⚠️  Make sure this EXACTLY matches your Xero app's redirect URI!")
    print("   (Check: https://developer.xero.com/myapps -> Your App -> Redirect URLs)\n")
    
    # Xero requires these scopes - offline_access is critical for refresh tokens
    # accounting.settings.read needed for TrackingCategories (Business Groups filter)
    scopes = [
        "accounting.transactions",
        "accounting.reports.read",
        "accounting.settings.read",
        "offline_access"  # Required to get refresh tokens
    ]
    
    params = {
        "response_type": "code",
        "client_id": CLIENT_ID,
        "redirect_uri": REDIRECT_URI,
        "scope": " ".join(scopes),
        "state": "optional-state-value"  # You can make this more secure
    }
    
    auth_url = f"https://login.xero.com/identity/connect/authorize?{urlencode(params)}"
    return auth_url


def exchange_code_for_tokens(authorization_code):
    """
    Exchange the authorization code for access and refresh tokens.
    Call this after the user authorizes and you receive the code.
    """
    if not CLIENT_ID or not CLIENT_SECRET:
        raise ValueError("CLIENT_ID and CLIENT_SECRET are required")
    
    token_url = "https://identity.xero.com/connect/token"
    
    auth_header = base64.b64encode(
        f"{CLIENT_ID}:{CLIENT_SECRET}".encode("utf-8")
    ).decode("utf-8")
    
    data = {
        "grant_type": "authorization_code",
        "code": authorization_code,
        "redirect_uri": REDIRECT_URI
    }
    
    headers = {
        "Authorization": f"Basic {auth_header}",
        "Content-Type": "application/x-www-form-urlencoded"
    }
    
    response = requests.post(token_url, data=data, headers=headers, timeout=30)
    
    if response.status_code != 200:
        print(f"Error response status: {response.status_code}")
        print(f"Error response body: {response.text}")
        response.raise_for_status()
    
    token_data = response.json()
    
    # Save the refresh token to persistent storage
    new_refresh_token = token_data.get("refresh_token")
    if new_refresh_token:
        if TOKEN_STORAGE_AVAILABLE:
            storage = get_token_storage()
            storage.save_refresh_token(new_refresh_token)
            logger.info("✅ Refresh token saved to persistent storage")
        else:
            # Fallback to .env file update
            update_refresh_token_env(new_refresh_token)
    
    # Note: Tenant ID is obtained separately via the /connections endpoint
    # You can get it by calling: GET https://api.xero.com/connections
    # with the access token in the Authorization header
    
    return token_data


def refresh_access_token(max_retries: int = 3, retry_delay: float = 1.0) -> str:
    """
    Refresh the access token using the stored refresh token.
    Implements automatic refresh token rotation and retry logic with exponential backoff.
    
    Args:
        max_retries: Maximum number of retry attempts
        retry_delay: Initial delay between retries in seconds (exponential backoff)
    
    Returns:
        Valid access token string
    
    Raises:
        ValueError: If required credentials are missing
        requests.HTTPError: If token refresh fails after all retries
    """
    # Get refresh token from storage or environment
    if TOKEN_STORAGE_AVAILABLE:
        storage = get_token_storage()
        refresh_token = storage.get_refresh_token()
    else:
        refresh_token = os.getenv("REFRESH_TOKEN")
    
    # Validate environment variables
    if not CLIENT_ID or not CLIENT_SECRET:
        raise ValueError("Missing required environment variables: CLIENT_ID or CLIENT_SECRET")
    
    if not refresh_token:
        raise ValueError(
            "Missing REFRESH_TOKEN. You need to authenticate with Xero first.\n"
            "Run: python3 token_manager.py auth-url"
        )
    
    token_url = "https://identity.xero.com/connect/token"
    
    auth_header = base64.b64encode(
        f"{CLIENT_ID}:{CLIENT_SECRET}".encode("utf-8")
    ).decode("utf-8")
    
    headers = {
        "Authorization": f"Basic {auth_header}",
        "Content-Type": "application/x-www-form-urlencoded"
    }
    
    # Retry logic with exponential backoff
    last_exception = None
    for attempt in range(max_retries):
        try:
            data = {
                "grant_type": "refresh_token",
                "refresh_token": refresh_token
            }
            
            response = requests.post(token_url, data=data, headers=headers, timeout=30)
            
            if response.status_code == 200:
                token_data = response.json()
                access_token = token_data.get("access_token")
                new_refresh_token = token_data.get("refresh_token")
                expires_in = token_data.get("expires_in", 1800)  # Default 30 minutes
                
                # CRITICAL: Xero returns a new refresh token on each refresh
                # We MUST save it to maintain continuous access
                if new_refresh_token:
                    if TOKEN_STORAGE_AVAILABLE:
                        storage = get_token_storage()
                        storage.save_refresh_token(new_refresh_token)
                        storage.save_access_token(access_token, expires_in)
                    else:
                        # Fallback to .env file update
                        update_refresh_token_env(new_refresh_token)
                    
                    logger.info("✅ Access token refreshed and new refresh token saved")
                else:
                    logger.warning("⚠️  No new refresh token returned - token may expire soon")
                
                return access_token
            
            # Handle specific error cases
            error_body = response.text
            logger.error(f"Xero token refresh failed (attempt {attempt + 1}/{max_retries}): {response.status_code} - {error_body}")
            
            try:
                error_data = response.json()
                error_type = error_data.get("error", "")
                
                if error_type == "invalid_grant":
                    # Refresh token is expired/invalid - cannot retry
                    error_msg = (
                        "❌ Invalid grant error: Your refresh token is expired or invalid.\n"
                        "This means you need to re-authenticate with Xero to get a new refresh token.\n\n"
                        "To fix this, run these commands:\n"
                        "1. docker exec -it nixon-backend python3 /app/data/xero_api/token_manager.py auth-url\n"
                        "2. Visit the URL in your browser and authorize the app\n"
                        "3. Copy the 'code' parameter from the redirect URL\n"
                        "4. docker exec -it nixon-backend python3 /app/data/xero_api/token_manager.py exchange <CODE>\n\n"
                        "After re-authentication, the system will automatically use the new refresh token."
                    )
                    logger.error(error_msg)
                    print(error_msg)  # Also print for visibility
                    
                    # Clear stored tokens to force re-authentication
                    if TOKEN_STORAGE_AVAILABLE:
                        storage = get_token_storage()
                        storage.clear_tokens()
                    
                    raise ValueError(error_msg) from None
                
                elif error_type == "invalid_client":
                    raise ValueError(
                        "Invalid client credentials. Check CLIENT_ID and CLIENT_SECRET."
                    ) from None
                
            except ValueError:
                raise  # Re-raise ValueError exceptions
            except Exception:
                pass  # Continue to retry for other errors
            
            # For non-fatal errors, retry with exponential backoff
            if attempt < max_retries - 1:
                delay = retry_delay * (2 ** attempt)  # Exponential backoff
                logger.warning(f"Retrying token refresh in {delay:.1f} seconds...")
                time.sleep(delay)
            
            last_exception = response
            
        except requests.RequestException as e:
            last_exception = e
            logger.error(f"Network error during token refresh (attempt {attempt + 1}/{max_retries}): {e}")
            
            if attempt < max_retries - 1:
                delay = retry_delay * (2 ** attempt)
                logger.warning(f"Retrying token refresh in {delay:.1f} seconds...")
                time.sleep(delay)
    
    # All retries exhausted
    error_msg = f"Failed to refresh access token after {max_retries} attempts"
    logger.error(error_msg)
    
    if last_exception:
        if hasattr(last_exception, 'response') and last_exception.response is not None:
            last_exception.response.raise_for_status()
        raise last_exception
    
    raise RuntimeError(error_msg)


def update_refresh_token_env(new_refresh_token):
    """
    Update the REFRESH_TOKEN in .env file.
    Also updates persistent storage if available.
    """
    # Update persistent storage first (preferred method)
    if TOKEN_STORAGE_AVAILABLE:
        try:
            storage = get_token_storage()
            storage.save_refresh_token(new_refresh_token)
            logger.info("Refresh token saved to persistent storage")
        except Exception as e:
            logger.warning(f"Failed to save to persistent storage: {e}")
    
    # Also update .env file as fallback
    update_env_var("REFRESH_TOKEN", new_refresh_token)


def update_env_var(var_name, var_value):
    """Update or add an environment variable in .env file"""
    env_file = ".env"
    
    # Read existing .env content
    lines = []
    var_found = False
    
    if os.path.exists(env_file):
        with open(env_file, "r") as f:
            lines = f.readlines()
    
    # Update or add the variable
    updated_lines = []
    for line in lines:
        if line.startswith(f"{var_name}="):
            updated_lines.append(f"{var_name}={var_value}\n")
            var_found = True
        else:
            updated_lines.append(line)
    
    # Add if not found
    if not var_found:
        updated_lines.append(f"{var_name}={var_value}\n")
    
    # Write back to .env
    with open(env_file, "w") as f:
        f.writelines(updated_lines)
    
    print(f"✅ Updated {var_name} in .env file")


if __name__ == "__main__":
    import sys
    
    if len(sys.argv) > 1:
        command = sys.argv[1]
        
        if command == "auth-url":
            # Generate authorization URL
            print("=" * 60)
            print("Xero OAuth Authorization")
            print("=" * 60)
            auth_url = get_authorization_url()
            print("\nVisit this URL to authorize the app:")
            print(auth_url)
            print("\nAfter authorization, you'll be redirected to your redirect_uri with a 'code' parameter.")
            print("Copy that code and run: python3 token_manager.py exchange <CODE>")
            
        elif command == "check-redirect":
            # Check current redirect URI configuration
            print("=" * 60)
            print("Current Redirect URI Configuration")
            print("=" * 60)
            print(f"REDIRECT_URI from .env: {REDIRECT_URI}")
            print("\nTo fix 'Invalid redirect_uri' error:")
            print("1. Go to https://developer.xero.com/myapps")
            print("2. Click on your app")
            print("3. Go to 'Redirect URLs' section")
            print("4. Make sure this EXACT redirect URI is added:")
            print(f"   {REDIRECT_URI}")
            print("\n5. If it's different, either:")
            print("   - Add this URI to your Xero app settings, OR")
            print("   - Update REDIRECT_URI in your .env file to match Xero")
            print("\nCommon redirect URIs:")
            print("   - http://localhost:8080/callback")
            print("   - http://localhost:3000/callback")
            print("   - https://yourdomain.com/callback")
            
        elif command == "exchange" and len(sys.argv) > 2:
            # Exchange authorization code for tokens
            code = sys.argv[2]
            print("Exchanging authorization code for tokens...")
            token_data = exchange_code_for_tokens(code)
            print("✅ Successfully obtained tokens!")
            print(f"Access token: {token_data.get('access_token')[:20]}...")
            if TOKEN_STORAGE_AVAILABLE:
                print("✅ Refresh token saved to persistent storage (/app/data/tokens/xero_tokens.json)")
            else:
                print("✅ Refresh token saved to .env")
            
        else:
            print("Usage:")
            print("  python3 token_manager.py auth-url          # Get authorization URL")
            print("  python3 token_manager.py check-redirect    # Check redirect URI config")
            print("  python3 token_manager.py exchange <CODE>   # Exchange code for tokens")
            print("  python3 token_manager.py                    # Refresh access token")
    else:
        # Default: try to refresh access token
        try:
            token = refresh_access_token()
            print("✅ Access token refreshed successfully!")
            print(f"Access token: {token[:20]}...")
        except Exception as e:
            print(f"❌ Failed to refresh token: {e}")
            print("\nYou may need to re-authenticate. Run:")
            print("  python3 token_manager.py auth-url")